From 107765f0e4b543dfc346851ee5b4605cc17eb1c6 Mon Sep 17 00:00:00 2001 From: rpolzer Date: Sat, 13 Sep 2008 18:28:57 +0000 Subject: [PATCH] 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 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 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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 + + + 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>72YAB#nv{f3koD ZbU-A?3+wGXDSne|TbPOW%^{)M^*+XmxFq{OTzw`R(OR zvl_b>8ksw5>02b_wm5l4+PMa=+j{u-??030tZ(jJ{Oi|mPA=|{*uqzD-dDEFscE0b z#?ImD6Jz5PuwmN~M~`qhMGbqmP<}y?84EV)8QbpMe_BFXK|n~fzH7m^??1NfIeF#A z{es8VsjoYbY=+Vq++N$&(iXBb%*(^aB`U>a+Gxh5&%?{7$i=A1 zq0nf;z{8-&&0)vSFD>0DB*@3eudT{#V8hP7QkOwNOxae8#adlurI3a_GbfA4$&-hi NB-uDmopfZd1_0U9eEt9c 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 zcmZ?wbhEHb6lUOIxXQp#R5vZVd}3%^QBZW=vbB2`t=ctl*4m`pW^-H5=+tTk28RFt z|Ep;k|NZ;-@zYnc7jNyFvf|l`H_uFq8dZ(>=`TE_1$1mux^U&rrE7N^x);u0 zzN5K!@%hWQuiUu5V*S3Or>?}L)qMT-{ot{Swk|;@&R$!;_3)khPxl=@|L@|NgiEyfWf=^aB=qIDxiw4J z**UE_8Cm#vdjv|vRTw4N?1e0QxH(EJj7>EZgqLuOm2lb2Sl6Q@x^e3gu^l_t=?N^` J=v~ut(sG0iu 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 T%WW)8dc9SJ@f-hE{l5tSvL2Ik 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 zcmZ?wbhEHbG-9x0IL5%>W+sy4A;Q4G@OX3Z!?kV?);Ql=YJ6|8-ozqPcT1^Me^pHd zo=2Mkv=jyU^K?>t<=xH2_Dr&cEpHxq#r zA6ZQ~&OQAW{kaQtAr_a>GN={ROZ{Jk6iN)p<3JiC3X?a-7rv_@J z25EU%$+}xePAD`^_7qF;SDBEn&oI=0;!hSvE{1vr9gxF8al*j&U!9x<|dv6i`KXEa&z->)X!yM;bYm% z$Hy-qz#_mWIFC(8ScF}Kb^iJb?JSE#PF=Wug1u>vC2@jy(ko3s=-~^Z#fL z%e|?c_xnWRnH3iUn8NSOTI=+IXZ}tu#$%03JT@&^$n#`|p~ezMkuzM<7C)*UBsi{H zJkLtR;MSx>MSi9tF^eS)&5dO;Qnd;p3bPa=nVcFuLKfU%a^m7>xuNC3a@+CW{W(%m z2YMYA<)3wsF*ZmFjH&6A>)G7BLVFu$&%ZDak%jLRgqnnOHq7c}YG+q{R}~wcxJu!K zJPSw6i^|6uE^0av8D&iZT$4n?W^RkjXlW@6{9NS~IJw|(UaNqIf<(fX)oJHT+%BzwvoaA^>Y>Tjgf^|?y{q@Kh1s4vrwy^TD z@P2T3xpyJQ$Ak}u6m$<5oQR(MBm9K10o_VmLXQ|6#4S8dgvtGI z>tH|mmPM|h#fITLqY__`13SmRhmG0`kCZKnx!f!4)m^WU;85P=a$HHt$R**4dOO2E z4X*ynJzF1`+Q?0ss>t^uf^o95Sket`2R@_bwss2v2Aha&H%^%v$SrbWRJib=y`B3V zvwBSArApqUe>axKDhf`y>^IYYk!13Og+8f=6a`vb`4_ZsR60y=F$!*GXy-|AU}ZA2 z>X>xF$Mi+~6uC(UxVLAnlJ8wqV&KH_)AX0uijFM}jMHNCG`Z%Pu}E>Tbp@C&SC9@W za8{Hqx^g{#x7H=6xy>dY9x-!F>d@lmuDGyj?n$Lbj$CXviaI*kxtg3Ag%X;Y6bw&Y zxWw8hYw%RjQb6FKX1#+M2iGc2gBJ?vo4PsJ+28zJ%cy(GsGD0-=hD>uZ7&3_@mXjr zI-n*0`GZ0S`9v#|Sl|Aq;nrgIV;L)16L%f{q{PRN$RV(& za@%`($pbkjdXmEuX2$K52>xQIxuG(|`-GtXE5T7m0BM1l&sASIg`3+OkT##22y3KySA z^BnYGyYbZbeCcX$CGNoe7k*2osNDE{oZZFY!vUrve~eBu~(xrgW^vX1`yBz5gE(KZZJ{VgR5)FqXRZ~v{)P+ENA}k6YW1J z$fUx{*68qMhld--grjnW1NquQv);V1QjN&Y!qH|5AF)*sgh}h0$Rg)^>W7aiTCu`5Bwt4G1McG}O)iu^> zX&z)geDwJ4lSk$rJ8|m5nM3F2Ts(XE#?^DDFI{K7dF}qKhmY@FdGPeft7mUs-g*7* z?Wgx&K0dm8``71hU;n=Qd%C?Z{$TNMfnVGl9~5kwIpwuPPE1H}sSyyjI%2W$;n7A( z%{-Qy7Z)At7j>?(IT@sKYNF<0*9jX8n9rtZC+$!YJjmi#J$H^+#|Z(4!_3^m8YPoH zG-)vN*ye4~U{G`xnI#+N^T6<+Q!}rHUXMXxq63o{=OLR8g~Ki#;$|~UUMy%n$RQ~h zw6GtviK|PCE7oHqo;*&Gk>Jbpp&?HsEZLIhqu`@-DNh>-p=;1*o)dkMoa=yE^ Oyu7?3crhOngEauS!yK>x 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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 + + +
+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 zcmZ?ry}!2w7I1A~%+AP5Mk8XFrM8!#}cf_N685Zc(7 zQB^>IQP8pj!nd5ks4Bp~AZR%g!UxMU2nt%xWB}_kU|<5%MoLPmp`oFnMsUlN7#ILr C>L0%V literal 0 HcmV?d00001 diff --git a/contrib/bkgrnd2d/bitmaps/bkgrnd2d_xy_toggle.bmp b/contrib/bkgrnd2d/bitmaps/bkgrnd2d_xy_toggle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d95fa2849601cb44190e77929d9f7b7fd6c154d7 GIT binary patch literal 220 zcmZ?ry~6+jNem1O8Vn2!0t^fc{0s~Xj0`Lg2?hoRA#MofW?*0dVUWau0|yxXGyG?0 zXlP()U|?YQ|NlRTVEE6#z+eL+T9z_QoH&s|2?~^r!N5paa;C7OV22Tizhtqok+Ptm zu%M%$k(Z#Lv!IBek&2**u%M%(k(Q&Qu!x|@B4rUlVMjqhClFsyP;imT;w6iXjg2Ad U1!qF^OU?wF2NH)`r^LVj0IjwuSO5S3 literal 0 HcmV?d00001 diff --git a/contrib/bkgrnd2d/bitmaps/bkgrnd2d_xz_toggle.bmp b/contrib/bkgrnd2d/bitmaps/bkgrnd2d_xz_toggle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6af8308d2efbd7177e8d3741f6c7f5a044148b7f GIT binary patch literal 220 zcmZ?ry~6+jNem1O8Vn2!0t^fc{0s~Xj0`Lg2?hoRA#MofW?*0dVUWau0|yxXGyG?0 zXlP()U|?YQ|NlRTVEE6#z+eL+T9z_Qnly<)2?~^r!N5paa;C6|i;I!6prEjbpr8>* z+{(+?$V*VrSx`jKNJUUYSWwW>NXyYtSWr;JNJ~TztRBJ_G*Vf$WU;X^RK4I#usMQ~ PGr{J7#G%$HF)#oC3Lhs? literal 0 HcmV?d00001 diff --git a/contrib/bkgrnd2d/bitmaps/bkgrnd2d_yz_toggle.bmp b/contrib/bkgrnd2d/bitmaps/bkgrnd2d_yz_toggle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..de9f4df904980afdb280440e1112e68295e13e0f GIT binary patch literal 220 zcmZ?ry~6+jNem1O8Vn2!0t^fc{0s~Xj0`Lg2?hoRA#MofW?*0dVUWau0|yxXGyG?0 zXlP()U|?YQ|NlRTVEE6#z+eL+T9z_QoH&s|2?~^r!N5paa;C6|i;I!6prEjbpr8>* z+{(+?$V*UASWraJNCiX-IvQy?3JMDfiWq5$2!hpXIXVgp3JMyjtXi_z*chzdSx|5$ S*c?I0nP783;!x|97#INT@h0m4 literal 0 HcmV?d00001 diff --git a/contrib/bkgrnd2d/bkgrnd2d.cpp b/contrib/bkgrnd2d/bkgrnd2d.cpp new file mode 100644 index 00000000..36cf45e2 --- /dev/null +++ b/contrib/bkgrnd2d/bkgrnd2d.cpp @@ -0,0 +1,318 @@ +/* +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 various other plugins +// + +#include "bkgrnd2d.h" + +CBackgroundRender render; + +CBackgroundImage backgroundXY(XY),backgroundXZ(XZ),backgroundYZ(YZ); + +CBackgroundRender::CBackgroundRender() +{ + refCount = 1; +} + +CBackgroundRender::~CBackgroundRender() +{ +} + +void CBackgroundRender::Register() +{ + g_QglTable.m_pfnHookGL2DWindow( this ); +} + +void CBackgroundRender::Draw2D( VIEWTYPE vt ) +{ + switch(vt) + { + case XY: + backgroundXY.Render(); + break; + case XZ: + backgroundXZ.Render(); + break; + case YZ: + backgroundYZ.Render(); + break; + } +} + + +CBackgroundImage::CBackgroundImage(VIEWTYPE vt) +{ + m_tex = NULL; + m_alpha = 0.5; + + // TODO, sensible defaults ? Or not show until we have extents ? + m_xmin = m_ymin = 0.0f; + m_xmax = m_ymax = 0.0f; + + m_bActive = false; + + m_vt = vt; + + switch(m_vt) + { + 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; + } +} + +/* + * should cleanup, but I don't think we can be sure it happens before our + * interfaces are gone +CBackgroundImage::~CBackgroundImage() +{ +} +*/ + +void CBackgroundImage::Cleanup() +{ + if(m_tex) { + g_QglTable.m_pfn_qglDeleteTextures(1,&m_tex->texture_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 zcmZ?rbzo!w13v}^1`P%V1_1^J27U$x21W)BusDbi;$~m~VKxQ^1`q~`{r~@;0R(Ls zY#H8&ykYn|>n{TnIshp|U?wI87BFB10|s1t1_lO@0+>7l3o9!~0Lo`zVFAgqvSQ=2 uf|Y_TVFAfw;j=QZvao<00+nZFWnchtVd_~~SyA+(@DZM2U|?WkU;qFlvJOiC literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_cleanup.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_cleanup.bmp new file mode 100644 index 0000000000000000000000000000000000000000..33cc9490f08d5987542b82cc6e8e51ee6d2d8f05 GIT binary patch literal 316 zcmZ?rwP9oc11|;!1`P%V1_1^J27U$x21W)BusDbi;$~m~VO9nP1`z(w@SoxFT4@H> zDf|ro|NjRm!HU5~Gl2kDnvoGCfXqebgOo8bFflQK8~|1g7D3}f6hKS^$rHzC0BHbO b2vQEU0AdBiJ_e8ikUW|LQMeF!u%!$De@_XE literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_dropent.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_dropent.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2fcdcba36568f7861194644c5084c40595258c04 GIT binary patch literal 312 zcmZ?rwP0ib12+Z+1`P%V1_1^J27U$x21W)Bu=pni1_mK+2xehmU;yF&4F4I9#*2dG z|Ns9Frm+x=P@|zzjEpD(ATE*s6BNJ|Gl3N_LCj!eWMpDsWMlw|fgH!k$N=IqF@QuM kTEO}tOfU(R2eUy0h!3J+7{X_QbHVzV;C!$xFi8v=05pmTG5`Po literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_merge.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_merge.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cc5e272cd28acabf840d47882aca3aa1f13e2bc7 GIT binary patch literal 312 zcmZ?rwP0ib12+Z+1`P%V1_1^J27U$x21W)Bu=pni1_mK+2xehmU;yF&4F4Gzz_=2u z21+n7K^ahr5o$C814IJKXJTMr1Peg;OpFW+j9>vUp9w@Vf(1YOsPcj0_+NC?8}B2t(C_ya2Kvq>uq529g6g48#UW!1xRh4op2r2^51=LYZI+i4Opk CQ3h`S literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_poly.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_poly.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ed720e90e2f1d8c01c3716d9cf691df751871adb GIT binary patch literal 320 zcmZ?rbzo!w13v}^1`P%V1_1^J27U$x21W)BusDbi;$~m~VKxQ^1`z(w@Soui!yks9 z3_ls{803>Tm}XR3qpcKAS?(; i0w1b@5o$gI3kwSq0|N^)R2=LCxNAV>f<&M+7775nv8Ri^OMS fU_@3A@(?l~>;!~)U@pjM5c{EAu#Z7bB90FLl~D$7 literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_trainpathplot.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_trainpathplot.bmp new file mode 100644 index 0000000000000000000000000000000000000000..66537dc1220871be9339eeaae1726ef84e5e0dd3 GIT binary patch literal 324 zcmZ?rbzx)xgCGV51`P%V1_1^J27U$x21W)Bu=pni1_mK+2xezsU;yF&4F4G-BBB}E z+S(W-BqSL4`9V4u82$28PsB1_nPr0R~}zOK}LWyFmUmJ)PSTJxVU)0inyRQ iK=}}Hum+GkSP6*7zyQ{T#D|E21Yj%(4OIu>AV>gPkQ(0r 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 zcmZ?r^wiLijx Svx1ml%nahAyB{orOaK7u4-I<& 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 zcmZ?rbzo!w13v}^1`P%V1_1^J27U$x21W)BusDbi;$~m~VKxQ^1`z(w@SmXpB=P@0 zLqh|Y#qj_Ce+Fid7z8siurMz8mjR-k8KR#7Yy`w!C<(Fy$^=tj NO<)$7K;tqn004J?3!?x4 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 zcmZQzU|>*SU|{(FAA&)AWNga7z@Wmwz~I3U&fo-A#|V|?VSzFjGJ+WxoULLq@^e#T zJX2!aOR|F!Q!*3tN@9}p^Gb>`lVS>rO3E@*%VPqHO2RTz%k_$qL3+6v7#K_#7#Knr zTp1!5LKr+5{1}`Wd>BF*To_!D>@pDGgxgh=TAW{6l$@%U!N4F!P(KsM8U|$j%9(k| zIi)G73QCD-6-BAVdKpT*Tnr2hU5td>!i;HFN@`kWUaCU8qgzD0pMQLCfRATLh-*-M zkZZ7iXppmOFfW%fni_u}SA5bTL5|Me_)Q21@(*wg3W-EEhL=k@GfhDeVpoa=$UQJO z#Jl+TXe!jyC?HFQI0m`LyZVJ{f;Fb3rYXdSd-@q0@pAb%`niWXy1Ob^>L?iUaw!)S zC8p;lDkSHpq{bH{rl)Ec8krh_w58^yWTt_%!@{#DwHPUWjg%A=^z{|;^7C|kGn0$* zi}TY;6v8r#OA~VxoVB$TQd2TZQd1OQ(OHaa5;SItl9d!-Kwn=WxFj(zC9x<)Avr&{ zAU`iPuLPtC>B~{~wY@4HyvV#(^1>(!v;m8R8kj7+e{G7=jr*8T=Xi z7~;X{&W*tz9KU;6-v47@0OW)43-QaI*Gx6fq_AS0i@TNA)g_aA(tVQ zA&;Sip%|Jc85tnr1q_J{c?^{dehi6VH6WWK7$g`N;r6&Nq%ssUB!lfQU?^e8WXNa8 z11Vz&WH4q3Vn}32VaNoVQo^9X5WtYnP{dHekjRk3puiBukjaqBP|lFbP{hE%pu@n1 zWNsK(v>3@eLk2wt12AM@Fks+dUM8T_I4fMN*bb66aKTn!3&ko!QP0&}k(gA0QX zgAY6|K)%UjC}T)u0QuI5p%fg)DGVTeptu0Z=QE@*lrkhUl%T{Fsc~QcjssNl&|?BQ z9zf+cBSSDl0YfT7GB}=d7?9oP0uHAzaG1b+jYyfG@*R|_A)%MYP|T16_9G$Y`3@@Q-gBday@)^n*6c~&dKxquB6ik8C zJ2Qwg*fQ`kyueUzgrOefmO`j|L1_pS!_HurBr~Kk#tCP=zp z&LF}N#K6Vyh57&gCI(%oEF{nHGiZYC|Hcdn(R_v+a2f?E0>u%C4rEYcSjQm9@D;4a z5nKmlGUzfmGUR|u6HtECVDM!~1m}o6hJ0{-E(e>F&7c6TYZMrY!F5Pw+% zm5l-7=N<-I215p3hCg7nKHxeTR6l|o08?`gs^%A14JZxdFjO+6gZ%=ke?e+t;ls}m z%J7^am*Erh|NpHZ2?&PzBa@+lfs5fMmUx-LkjW6wz{T(zDP9m}c7pTL8?gPboCGl# zkv8Tr{9y2arVUV@!Id`FfYZi1EN)oFum+qq-XpmIl-EEu@iXiL$H7Oi8$h)&ws1el zu#SP3;R9F=ZPNxKY_5RIjkjPsQNspY-7+vR@H13_!~HK7KhI#O0*CuQBtOHiD^ z;h^jN3lU~uWMNj<7sQ>>zF#P`yEtkQ*f++aJ%*+C^KT6lBG%YQ)NFgM( zxI`f_FGV4|D6=G0L8B-&IX^cyH7`XWEx$;iI6o&dML{9INFgs@p&&n}GCe=9Sd#%{ ziUk7$1F9+hc{!En29y*f<`owt7NzEupy}sD*6*JNYJ4y-Fl@8~wULnfz$d^CH(=ml z0aZsJjv|O)P-K90X!J5bod{1Cmv~1f|IiQyLnBKDRt5%;fRDeU3xlCCsFeriJNx^& zdAc(g8XGdOgH$-jgH*&DxiAUx;VCqmQR!Fx2rD3<3}tXMZ36AgG`vgCImO$|=;%4eUNc0|p_87}QQ< zgq?$n4?dqD}$l2 zF@q|yytBWrucMy}RNC0c+}w~s4OM|(NRYn|RDq#^p&^4hvV3r)uPaQup@AWT1_J{u zSwp+zphV56$WWe{2Oc_MU}9hl3yu$S4GQ-3_lx)RbMt2~WDsIt1P5Y>V~D3SgNCl5 zCW9seqavuE%joPB6bwot?tbw;u5KX=1{I(*?!%zZfGic_AHZNxVPs%n;KN{mA`|54 z?g7$e0+usGk#q773Gw%3FsQHqOB$g_I{HO27*x1{WsIRRPX0kIu0b$|7#M=AGewny zxWvE+BxQyw1#^ml2}sf$RTAzN0}GI>1*$B>F$QiRDNCqSh`+y2h-W}BgF%IXfuVs9 zgEdsb%_q_c8@Q;yV@SMjs85I|Sci#$4}&dK4(cys7qEy^>l_vn)om{AWOP=Mz}%@H8ApFaD+;^ z`S?4Ac>1|R^ceauI6)=cgFFKQT!X;kpa=tb*99u)ALQxk7ZUH`9~8x)fs`sj`~zx` zxR7wvWN<^(7Umihf}#zSn2_~>BS(|L9aS4B+#!}BSr5?$6#yBm$>0Ii=IG-Cb-bIK zo0|`V7gXHO-_gg@-485h00uq`{!lrPOK><18ZS_{1wb``k{HBMU_;~KAplhl3xYtX zda!mB8^9tEZ$R~c9HGe&1l0jC0YwW$2t`kbe}E=KFevZFd-}O}Iy;8=2gSQafO4Cw zxq$&gB$|}7BRH|Tnj080M4`#~g@W>-tGR&@Lo}LHuyYV7P+iRpj2U9k1?vjzJ*zGZ-)=GBARMLp@!B z;~fJ6;zK+`d_YOx6;!oOM#y=(__+GI1_wL3LuCyarXplLTzvw3{asL17%@yosDPUt z@9OL1>f!>)53U9V#tbtNs+|3O104M#A=R}(g{y&q3BzoJw7-)V#DPe8)YZVilwmH| zTacRGH5}wwLjwbb#RyUV09R0QbOjaLOAx}&KK{XA{}>t=F)T%h2RnvAg^d}Op$f-4 zf{GwlLjx0rZ0<_xRQ#N%B& zgIt}#Y0S{Tf?+kfbU=`6m?zA~mJDkU@}U7Pjv-J_I5Mn7NPzqn@9ZBC3HON;!#X5s zS3j3{Uq?@HA~ZB`W>}Aq^9>4(ck>JihPcPjfMFxLoR6y^*eiwx44W|Jj39D`44X0K zj3IJH3|lbeOdxW`3|lefOd)b64BIf}%ph{64BIi~%pr1S3_CF8EFf~`3_CI9EFp3h z47)Jp3=JTXmJGYGNJ2x#kzo%ONodeGG3>=62@M-(hJ9Ehp@HMVupf&gG;~}U4q%al z29F!VK~zZ}M_3HIF&qL*xw?3U#5?=AI)cj|Ln8x*qiEvskh;Lt(8!SC7?PZS0H|(u zH8e6}IF2M73Q0mn#tbJAqHdmkpxV>b(8z@0BtkgAF&LVVjZ7I%p^3-)c=~yRL*2-X z;WWBraDc0`rz0dO8<{hlLC6QW2Dmyx9bv(67DWPD`@4erB=^9q=mN>p#zqX+kmW)^y&Q0B!Jxtw)bzcMAq%Rz!A>+bVYq=T3!ayQ$e1$R1j~Sm zYe>foWSofs!$Y{RYlJ5x+)NA^9)U$cr9Y@nbd3NTVq(Pb7$FXdRB#)|)zHM4;R!+_ zG#FY37@C+cJOzt8`}>7}TJj(RAx4=pJOj%hM9dgofJIzf-5f)GLgEoJ<_s^vGT_cY zfTJHIpiB)I-hhQcMP)of*pT5ZSlG=!$k#C>-p$_+;wv))h7Vu~&`hMKU$ARX2rROU z3=9}P!{xwv0+jl|^?{LrA;T9`IcI-ASdbeT7%_ZBll1__bG)asKe%EtGB9TNhN=QO zk?H9QwZMeoJ6u7SYmgJ7C^j-QVE6&6CgVXK^>hu7hgWXK1_liOFr@uLeL=MrD4dNA z3>p4oDu6a_j17z!7#N|g1i0xSTOfLk85pt1dP2g}*uaE=2}3%{-{030>~CWOQwC{qmK_PhAa#X7sUw=p&%+0`%K?g+w z9|9~6mhjxmEfLP=<_E2!6p zVvz}h2SPyrsIl+w2XYbvXh6lpfWaFf>+k0ZEp*)sObi)(5Hca*{s;D;;??gD@mxVQ#`dVy{RCMFF2$SOSjLZI~}q)c}+FfnBaz^;M;>}oTH zKs0Hjlw)Gf5QHY{?1%_?6AOl5H0cn3c#<@+WC%gX`a5}n8dbioexaTruApAAn}LM^ zLj*zvxSj>ocVLHE7&1g6iW~WR9y#NMr!0{B<+1G-1d?s0j7* z_VW+-L&%ykUHvW&I!jhp8p#mi1>JksF z1{{5SV4>*9(1cJG>KEkd9`E80?qIkX8aOdDqf3Gef;+;Qp#>rD;^PCEz5*!$4;H%_ z8n`gDqN;FmbO9A0(1k7u z8jONsyzMUAq^yiVWteTa4SPH&WvF;LLH=K9uKLA!Je}) zV3>!HhXz%Mf4sB5pC4o}(#_DqkYPSTO|WZFm}^kHQ)n=_AT+cvVpxEXg{Ee31#M_y z%&-t91!=&!_&Xz8Zo;q#CLaLK&5&Zv&p*V|4Qje6!(wboASDFE3uX*U5K5p$8o0&- z7sG}Y<_t>_@(3>?E3sf$hD}M3tGj0~v<+=&Vac!@n<7xtA|6$xBf|=8DiNauZiW_4 z3@Z_8++9OZ8@LdkIWw$6s0qfd#)V-uLJidSE}p^8jzN%QXW`1Q2B84qeMp%OvB!;J zErt?b#{f@o@dZ|5X~3`np#)Y*fz`M`Gl8Wc!$yP>s6}8E!LH!JJ~u;4BZf^#NfDHpCz^hFxfCf?Pq}Txcr- zQl2}SGweoF<>DEHC|4XU81|sagZvci>IxbMbu)CdWY~))@8KBa67LBbpL8>HbY$3v zCLQeI2rA4WZggVUk0$No@9Yg3s&g}RbY?h!CJid};Dw%}3&TM)c~^foh{>)DhY(VZ zLCzlW@OXAIU^s#%haAsNh73p1q(OzAzn>4JKyWf*IEE(g>gVF`1`l~BV}|2s3c$r7 z!UPkB6KD#Y99=y9T)<dMCv%3= zFa^Q!{?5+v!Oo68uAzROA;Iwho)NC#aUnNDX9I?_2z8*cIW!nHIO%3&Y`|~{AqyG{ z3-JUsvB6_)Zbrt243`nop?;pu{w}Uax{VmFAf!Q~M@VwU3|C=t0rA0sKCsdrTm*w6 z(8$7&;Wnxg$j}j}407`i3I`=JSb!Q?7%|*IRqW~;5E2>h?C%rm>lY6l5pgrJFlM-m zs?N>R)yE|s5_Ivtp25D3AYxjKhH@~n}C8N&lq#eV*eM5AhFywA+j<92uUVD)D!5f-Z7`l%f!qIWas%RR&t00J0EN4#G!9 zAZnc%o}sD@_Hzsf_VCA2%NSX>Fg!<93@Z6StD`_IB|q@$2sa}OSB4j;sz4sWQ02z( z5>=I-f4qxhh+{mwpf<8JV0eY9CdfZL9#R`)R)R*Bh77M!m4kyP%qTv{H89jO2pYkb zMhtIImHGL{2lxkjf(D6PU7+TJbFh)6F~eI_wV-kjoU*Vvz=Yu)s%o%@LV`m5oE>55 z%hHtLJ*py*r`%j&>DkhZ;RC9?r=PQb5NN_T9^@Zzv{{-nd_+|Q4HXwxAJ-69aQ1|G z*Mi{_s!~vD@bz>CCF)@RAZ-4yWcZA#1(N+h$p@i;!#9}P;CP6apfWBDKVdQf@h*=3@veUEo_?iwk6o%gDu)K@d#=YO-)KV-P}90PP5XJ0{?$aWQ8QMpFYa#M3tbw4THd zmQ`FV7(`&ooWZq&lYazgMJ0n2LKrfd2U=XnAdL_W0F4}g20XwcBcORWIdn;|643B~ zuPXy+Do!3lfqzg4RJ$>Q0*0)kyDMny)yTkvL6HeG73CNZ;1e0|=MP$HVhkE-Rsjo# zdqz1qI)j>F#s)^l45~0e(7XeKL4~n_kqLtuOwi5K&odZQu^Af}nKGy&EDdpV0#&}C zrHBws1`HZ7Sr^b;Bxp3t#mAk&*udP#kii-u2lgjS#)!cNSq3~v44*y&X*FiBMJRyG zbVBk4*kBU|2ZSsneWq2hBPf8(3NxFa#i^!ce3P83GYfe*P{&{sGQG!9gGsEsPj~5OUzXuYTddpir^2 zFlGowmvRjb2kSCn2tk(ug|elEDMKilL^#ASGlnoU36pTgNU&k%4B-eVP-g`$W5Eys zlW~d%wIikApmoXyh77r=GQlB{KHw$H21X2dXi}b05E)~Je1r^W#LU|jG^=e0TGm#8 zkO}p3aSZ}Z0fU@i%20@q33ddPlhC5jjG+i216jT8h3_)YTrRZ|u9-!ePL(mX# z8M+*(fn*3;^Hz>7;~wPd>c?PcXkg4xfiC9+URDiS`BsT873u2Z;~x&vXUb58F6j#@ zKSLZrvStj`=(5g{j$jMT8EVj_+=Cn=LAop$YSE>9KntB=@|FyB=<+U(LEaEw8X7Rv zW0QsX)XJ3w}wUxjp))~%Rwa+$l->@3{B_?K(-@%ZD_*Kj7rrD8N+-mN#4wx(ZJ}G&N2POVHIoR3I#J zW>|`@1ZE6Ekqg5zbVU$zAnT?fes*P8j;;u160$Nkh85__!u?zzg)k^=3=J4oqAP%G z0&#K-0{6*W4GkGqp(}9;@pFU4prH}NYAo`>9*!>HrkR_op)tc6EGpnWHZ);ai$&hU z)7=A{$-zE0G-X(auE@nR7~G3Nm}JJV9$gWdG3E>#aH>FCCVdAqlBx5N>c`*oKgF z@eB^|af}4jUw-c3>Jkzvt_<68DFWw6h$=UR9k^7%N&`@8Ffw4+iBJcXK`c3NH8Nz_ zg(@Ez;2MuyCKwqp?8a098)9@dGG^F=DGiyV2D{YAgkdkH0@x%tM2RWGK1?OxW--_g zW(@lg(%>+N4+sG5`*3qLvS2uXMJ^s`-Gr-=CBs348c~2CI_l}T!TU&%OKrcO$-^%z*GdpL%r$j=obR($h%q?Gn~h& z0<>)qY>R~n!vzc#pwU#&nm&jMQ-+HeDnNsHZs2)qh#E77O9(Zt@zB9Yw@^Q4(8z={ zWJR=_tD^zKRb+MkVXi@Lpw<#7%{Uq|Ttkrz4e@t_OhAJzaWrDMjx6uu8RiL^7Iun^ zat#8H=({=^Gu%K{;tw(cl3Nj#oudiEO)QEWoslX;M^lDd$ZDJ%T|iwiP_x26$S>5_ z39@j^)zOUMHnKXfBL5Khc#*54Il~=f1yF-vqqMG$77TZhD zJVaK5Jf#7eseObj2{!;aL|hpjqp1P81{89TSaf4}f-E26?+=<|0;z$x(#e3~DY62H zZ_$R0T%8OVo*}CWa1HYH3e$O=HSVIY@) zI#$8Ki12kXVR(tI&JQ`7olF^CAuE7Rcm+6u4ter}q(F#`W(=>fC=3mD^>_1w&X>D7 znKQh>q6p+N#3~$DCkuwRSX4m^iAc!SPf*5kvSfINMgob>B)Z)$44;sdLhJ^|Z3uLH%+<-2 z;WM%tcf9U%WB7tcG1z_11`J=3)xg|_62Z=f4BwCyK_l4N$1}jm-!aGqDPEk77`|gs z3N4=@RvI(>Kvog}8n|-}LL@k66NaD23P7bgXc-bH3558ExcY)NSGl=5n=<@DRu=3E znoI;WUQiux#_$_iS&%DuTc@w9Uq~>htoH}+$aHgcHfQ+51loSf0N#ZA7`CIIfuV;H zeP8=Y@Gf@beeIyLbU>Jg#hJm1AqITn3+S|@7zR&<6owcEcZL#%Y_t=mVi=McK<7_E z&cjM#h=HD813DK3bkbA|_;el6c_E-PRY2#v=rLq~&p-g#^#4E1F31T;>ELr$av2oh zr(G2@#4z}R&uJ)P$OoT00y!ZHbaGQMLn-)dDbQI$B@CtDbFcClVi>|0oEa<`EWkS5 z7@QeE?ngdtPM;x>A&sE|>axM$o^tTHC7^SNiWwm8nZ}GBU#BqQ3zSSe{hWP5U0fLq z!Llk0|NkS48etVRhKm}SF&HwS@9HM`ct2+bkiDkha}Pj=9zZtRkZeo=gQttXH|Ufx z7x)_c|No&z8ykSnPXIT2NH)0&*<_Fr@sQIJUNeI>uY(1QT^L>>ct$P^FF-s;KWA4T z1}4Oz44~`_+0prm8RjxWu**O*g`V!DpN-gs>^9IGxu-M3|NkI&K{}A}@S_kugIt8* zegShKEpuZRhHqdl>JbTF!D0|4pmPttgZUuV4=@WfP8ZDZ2h0Yab?^(!gdNuK3Cu${ z19J4iPp~NbB!u5!F2whsvnJkv`Pe)HKS$ynSP7i_9?XTf(#VD3BbbZi5hE9d4`4B< zB}Og`Z^3-1%OHm;`~?d^*#97G(9sJ2!O`U68tm!r2cFt;5A}3$g?Jp44M34-WMTk5 zkpq-=A$E`+jrYKL0Av~ig8&nH-i4eTf?U>u&f0V#aMmU_0|Ubb@L4d#pP30dC-Vbd zJy5qGowEr#57UMb-`Sj?lWb7WwgH{N_<{*^ju`ec7=;)a7(CcO@}RSBFwc+!otIX_ z4wHwR>4kiL7wCMiHyki|=s7Y-ZU&vtQ@{&4qX%^MjwkpmK~m4>najYy(833@1N&Ju pptE2+__3V@13JwIlzJfbs)8S~8d$xGPy;H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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)Nl3>pj!3<3-c4EziX42%p4VDSUY3=Bft5DXRo6aW7+FyO`w4gde+ z!}#=q@sEU|9b@K7QZ-*w~%_ z^YZRs4F^=cpm3~*hUfnO3=I4KCnW6u@8Hnz-_vvAe;=P27-nGVzW}xW*nb9w+yC?O zZvT&o+5SH~eC_|ZxV;$q9UT7urv!t-puGI;|Jc}FV4R(O?SFLiw*NUfH^BBn+_&>T z1H*>@3=G%)Gca8L&%m$?>PJM{u(T}t55jJ4-T$kqK7-u}@;5|(!+!>bt^XMqy8bgT zwESman2Vu5De1`n2rjLNhRcl?Vz#`A~UA QC|>|BF9=cswvB-S05~EMi~s-t literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level4.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3783a80341059f6a66047506e4eb1719d1b06c20 GIT binary patch literal 198 zcmZ?rJ;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61dwR~MnNzil?EvXiGw8t7zG(X9Iz5FAt=ZQr3Ik0AOlz_ K#C(u$5C#A=91(s1 literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level5.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level5.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8f62851c5af230c3cbe61ba5279d7cd10a30775b GIT binary patch literal 198 zcmZ?rJ;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61Q4_U1B0NT0HYuyh=7PQfS7`Uf+#dho&lqad7S PU;rxt2@8Up3bqdbHVF}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;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61dwP320=jqMnM5aFd@hwD98ZfW6&^pn0k;w5G^3hf`U+S Lkgy=gsbKp6C=U@^ 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;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61dwP3K|w(fpMeoVgVpj!3<3-c4EziX42%p4VDSUY3=Bft5DXSzU|{&qQ2!q{X1j3|6j{q@E=5_t|BRuCX#Mc80l5ttKRDAkNDV$b$n^sNwS|;@ 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)Nl3>pj!3<3-c4EziX42%p4VDSUY3=Bft5DXRo5eWd!GHr zhwGxrk^`y1he2xaVSIW)a`^Oui?L|6=2#mz3zW_)BOJ+S~0TzzhO}3 zf4kZh|Mjycfw6*n(trDkdH?lOJOAsXcK?@mjsFi8>pcXf-J-uwvw>=9GD 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)Nl3>pj!3<3-c4EziX42%p4VDSUY3=Bft5DXRo5%&N8GvL7u^Z)Gxrk^`y1he2xaVSIW)a`^Ou*E!x;MQ ze*R}LJocZ#;^BV=)2m?h*{xUq#}sY~dyp8pJnC;l@?B>cDY zt^RNARsP>Gu=&4@cjbSt=t=(zdmjFG3TgpkP#7>6p8U_CIs+V@3|eddGpJ1b&mfWX zpFukBe|GD&|5+_p{#VcZ_CKZO`2U3RePI2CJrBS%gX#7E3&tP`vKZEhP h{|r{I|1+4~{11`?g#kW{5x$@}Lc;j;g5(CfegM>|VX6QC 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; 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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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 VnZd)zkVZoAK~`v1!##Gv008@{$fE!N 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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

5`&<%bE7%r(I|5 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 y1z*6z-m(4=XuldSUjsAtK&`5x1)8JHgi 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 + + 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 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 literal 0 HcmV?d00001 diff --git a/plugins/vfspk3/vfsq3.def b/plugins/vfspk3/vfsq3.def new file mode 100644 index 00000000..885ffeb9 --- /dev/null +++ b/plugins/vfspk3/vfsq3.def @@ -0,0 +1,7 @@ +; vfsq3.def : Declares the module parameters for the DLL. + +LIBRARY "VFSQ3" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/vfspk3/vfsq3.vcproj b/plugins/vfspk3/vfsq3.vcproj new file mode 100644 index 00000000..e52ffedc --- /dev/null +++ b/plugins/vfspk3/vfsq3.vcproj @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 zcmZQzU}RuqP*7k1gB2VM3}Orn3>pj!3?Lx~29P`lSdM`OLV-y}umqT?AScHlz|YTM zVQR{tuByr)EF{Db8W_l6Yh}ftsjkkTp{B+lA}q{csISjppr^-RW@5r%WnsY}D@0@-tW1X5igJePvQmc6@7^*5`S~(zT)CVfDlC*C zJUEEq#`&`hNA~Pu*tcy9Lqbe6Lws};LtIoO!;{-L8E#%U$8h!RX@)CjPBC0Mb%No- ziDL}sjvZk*efS{5sY3@CP9EIPaN@u|hU5G9GGwKtGNdLZFr*~JGbG2yFRA-*tniy`-XK4+t#mT*t%{F!448O|O(#Bl1+eui07 zCNa#MJdt7g#C`@}FHZ&^PY;F%AidYGFxcriww6fUtqX(={&>Di{}_tELy;@ zWZoQxg|lZe%$qTdAv7qEAvnOFA;{m4A<)m4A;8y%;oY;x3|rQ%VTg_hX9y1oW{8i8 zX1II(D#OLoCmBAze#!9u<#UF2FP<^{{PvZhro5D)v!#ilqq&ixt+Ae=wV{rorM{M- zxvqv`a$gU_q~30Z{;m#&p7vIT?zR?&uGVITnNub)tXjI5;qtjN3?E*edHj+iG%wXjvv^|aBTk`hNJs-Gu*v)nc>#u3k=Z)`r2> z+KR!(%97#OfxQeaPL2$Y_I3;ocD4+j?rseJzCH}T-d+q}KD=Z2{P6=rb6qvVyqVJ& z!h!=CQWE1Co<4fO&`?v!;OuD6prok4pr|0vpdc^Dpsb|GpsuFM;P30rprxt7prNkD zpr@mt^SO^A?ibA79A0cx0AQXt?l2z!|fEZj*2o;}}0+NAYqX{B2c)|K*A=;%yQ0(Dd zrCJaNQtbES0En7)V*@jYhGQ-f)fpfWKd|xAg*&7{Jh=4t?+PM2;=X_9cRu*y`}dNk zUMH_X>_MoAE66=4A!6h{L1<{GU;KSlBau))kpiO~?p+G*1*t}t!LCH4ocJ9Z{0fcYcNlej z|NdP?;v`raWC9p7e7~T&!LPt*M|vSd;u}~PPB46zUK|=KQc!T=h6;MvfE3Be8reUQ z4)r?J{C&bHPLK#Po*{cd0u*DRr@nvx9y+lbE%e=u3RJ)mcN`RUUFi|skRXE@5h`#B zEX^4Tc3Y5uNGFO3ezG9Lzkip03YMR}bm`JAMwsUB-@ikpzk3;h<-fBecd7A$O@t^) zhEOv$h=5ZOgolNc_Ir*+6iH}?8WIN^laM?%J@~}_|M~y_|DXE*|Nq$k|Nlq+|NlSy z^B>so%Ri8jlm8$iBmaX(LjDhnf&4!P2KoOC4EFyU82^7b!2JIK0|UbW1_s6k1_ovZ z1_t*32blRma{m|@{{LWLsQmk?0soH*3-%u$?AU+)0NG*x|NlSx|Ns9( FFaYfj{f7Vm 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 zcmZQzU}RuoP*4zH0D%`w3=C=v3=9GS5WWT@0|Os31A_(w1A_ts1A_wtNIeTkDFXu& zgaVNu)gTOFGcYuO5ko@*h{XUR8yXlG4jeeZ&;VjE{AU0mhX4N|6vKZIli~mW{}An% zBqPFXup}CvkrC=#up|QmA-*7^fFM*Um_p}EqRC4#PM8Q*2qh#rDd95;&g7ghQxNI` z!I?8>g7`CM&IE}_&YU?>P>^$ipdg6Fz&R1@bIu7Mn;9i1aDr$C1_+;lVFEND7{T%k zi~=A8iXyNsMv#GE4NzrZHi&@pSr}MYTpU*)z{~v}w`Tzfq4>vjd|G?zHe}Kt>x&2@L!~Os2|IYtc|MU63`p00i{?-5AC83zP z>zZ!%b}_^3_G`A;yCf6^cmJ#Z-Tu%1|NsB>Ae;Y#(mBX&ptR1&07>T!P`807kQ@ku z*x*DClB6)5L(PN52|QsyV-L;;CqY5Ui4!LZG6)I^GENW_1j&K)2|{9?QGmh0#Zkn? z!GXbr#X-cy#eu 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 ocmZ?r{lNeMW(*7r8Vn2!98k>2AOV&*#K6EH#0|kmeU3K|M1g19hwBFqEX1^}jW5kUX| literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/brush_rotatex.bmp b/setup/data/tools/bitmaps/brush_rotatex.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b079071e60c0aaa771358f90434bdfb5c4a044a3 GIT binary patch literal 238 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg39MuT!+(bV3=LT22nz}#0YyO}Fi%KO z5ljmzK`0m>CJzx66ciK?6ckhxR1{=j6ciK^5)@!y01JS{L1r)tg2V+tqF{A!je>#z DAzclJ literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/brush_rotatey.bmp b/setup/data/tools/bitmaps/brush_rotatey.bmp new file mode 100644 index 0000000000000000000000000000000000000000..efcfec247e921f3e77c190221abfb2c9db02c3c8 GIT binary patch literal 238 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg39MuT!+(bV3=LT22n!0L03`@d2tp|- zLTEuKUr-P#4;2;^05KE=1sNCx1qBra1sE8>G+10nNKgdV?f5pav-%JJs`axGeBk`3cf{{aKz{{svRAV)y$0{~vHFm(U` literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/console.bmp b/setup/data/tools/bitmaps/console.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c45c686699c3fc6838109f83def9a075a591b805 GIT binary patch literal 776 zcmZ?r!Hm5G`qGX&D(A_6`nCV3%82S;5U`U|_JYuyA&A_VDoV z_xF#Aib@3Al9rZgYinz7?_gtNV{UG4Wo2bzVxq6FZ)j);vBk{H%)!ASI5;>yJ|3b4 z8541%(ua cn3Na<1;Gwy1ewDKF$N^S2(|}oAjnV#0EEK{$^ZZW 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 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2`uD)hW`u<4F4G#u*eZ3BnYyEfkA*# z5Tu%sfdMQaz`y_!U<8Q?3VJ($cKV%;e&0D zb0BC5%ThNtx4OEzIdkSLT(BS@ARsI>EIKL*q7p(PtDnO#$I;QTva)iW4|gA5-@w2?JnC(2ZHtPE`uqE*Oqt^1 z;^N`q;p^iIQGb}>@Lh(xKN)`BWVpGGVci^tISUyUT3K1;<>qyFcTbow!NI{1q{Y+o zEW^3`4EKLB{QS@GAAAj;6l zuMA(~Vq;57N*fy++uPf$tSoJ9Z5@)fTBC)3Zf;^(^DyYh1L%FD}Z zYHAuA8_mqjt*oqU?Q9(#9bH}BJUu-(C~o}9^7X9P*-m6LWKOD{Ct|J6lI5M^{%@FHcWDKfmDM;E0Hb*x1;lq@;}W^xT}BqN1YmiV7nm mBU6YLkS%tOPL6J_u3lbVet!N?EpcEi85y~`xkbgr3=9B$)j*a2 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?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2s0|yxXGyG>@`2U}Qp`ihXD(u2a zXg~q1Oi4+B#l^+Ng-J<407N?|DHuWo6oerH3WkPYl?uYb!omVdN(zRChK2@8N(#at cVI?Ic1|}sXB?Shk@nBj(NkIY3XJAqS0J5YQbpQYW 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2s0|yxXGyG>@XlTHp0=KXd3_z4H pDHt;_FbFbZrx`%v#vlNZXE0_`Fc1_J1gXbMGeFfbGATgq0RXoB57Ynv literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/icon.bmp b/setup/data/tools/bitmaps/icon.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e270952b393fa7a60d566fafaea28cba63cdc761 GIT binary patch literal 3126 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 b_-Wnyq2bQq=*neJhoi5ChM!VDF)#oCNlI?m literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/modify_edges.bmp b/setup/data/tools/bitmaps/modify_edges.bmp new file mode 100644 index 0000000000000000000000000000000000000000..620fd324cf03ec74c6158a1ae4c68ad34485ac2d GIT binary patch literal 316 zcmZ?rwP9oc11|;!1`P%V1_1^J27U$x21W)BusDbi;$~m~VO9nP1`uvwU|{(F|33o= zHZ*{E3=B-@fEf-TGR%xnz|0I5K=2vC0!VzY05TsWKrWwwfsqlcAL<^E-#~mOn1LVx Z6c;csFo5~UD!@)=f*Qfhz`(@7006U)3N!!! 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 zcmZ?rwP9oc11|;!1`P%V1_1^J27U$x21W)BusDbi;$~m~VO9nP1`uvwU|{(F|33o= zHZ*{E3=B-@0HhIuAu=E%85uy387u&1g18KfU;%_YBUk`Q9xQ+?4^lw7Jj68&42)p& an3x%%fEmI^767{lNdUrUVuk}I1_l6VG6~86 literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/modify_vertices.bmp b/setup/data/tools/bitmaps/modify_vertices.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6299b01e72c0fda5c0f318a133fc6eb879bf8e85 GIT binary patch literal 316 zcmZ?rwP9oc11|;!1`P%V1_1^J27U$x21W)BusDbi;$~m~VO9nP1`uvwU|{(F|33o= zHZ*{E3=B-@fEf-TGR%xnz|0I5K=2vC0!VzY05TsWKrSET7qEV)dtiJfsDW@kOn{M* X8Kj@VE8W}!0=x| zf#JV_0mFX>2ZsLv0Sx~mA{hQBCNlglEM)lK*vRmI;zWl33l=c^U$~Ir|E5h0{|_Bv z_od`tgSVD2(u7{JNQjX!x_Ru|hn?s*KO`3=GKWmyMB;krAYgnV*r7QIM61 ziCvhHkx`XFl97>7S(Q;z8Ki(wOcmKkka{R);bY|E2C?}V`PsM`1(}!xg*lbPBpDP% zMP=0_870+Z86{<<7)8{P3;?NzVKzoKP61XXW<~)fCMIr1HYP?sCI%&t10)#~7#SrQ z7#L+47^D~#aOxKnVq{}tW8`9DVq#(BW@6-KVo+vOW{{Lr6A_h!>Q@FC0yhC>H;86q z#HnA3Q5mKm>Nm!o}>GYd0vGl9wxSw=Ai24z`MQCaLI8AJ;zDImba$H>SH zE=L$-89^FBnGazp5(`x)geM@t#>mge4K7C*)W8nMsUOKGI8#zgR78zY7M#&RSOEFc65QXyjXgGzf)Q7_9N1*!*Fm;^vE$_Osi;P%28FiVkWWo1|5)3DX9rk@R6@!WH6RVAX^qh(jA(7)dEsCR`y723Ac-f;hCng^`qEWx^HW zU|`jRB#1*BTo_3yRwi5_4hBqnm1P+fK-DEE?aNCuN~x(yF-ma=FmiJXGV(ET3JGy@ zb2D97nlavG%88VDwVvN!ZVEs&djBHE-!UBw-<`KvP!cf~0`V|>P zWI#n6$j^)*{c4P|3XIB7{ah&e`S=le8RmaUQ8lpJLCqbI`xzJ{MMT9Ip!)e4nL+Ls z;DorJiItHZ>H(O3DMnc(X+~vO_$e~TN-;7jGBB`#!jB&<{J8mrnbGvCiGm_Z85Vz% zp!No+p$GCUBO^a*{4s)CXkZgy_JbN2YRc-M<_4(jPzAL&Ky?obs1X8E2`xle-~u2) zMED`hL1jTy;U-a4WAbq8gvem(Koxbt$H6{ gurinu6jFlFFg`J|f&vgdMuretNfAQB_z<2T0Mj538vpr)krxzT1j`G87-0Pn^-%Mm J=0mMh0svmB5$pf} literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/patch_weld.bmp b/setup/data/tools/bitmaps/patch_weld.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a0c99730243e412857b3d7e4976bc71caa97ac26 GIT binary patch literal 238 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg37ljD!+(bV3=Iqn4F7Se!7iZ$1&UBw zNk~aaiAjk`5hTE%priy6U|>>GQc_e=VqgIAg_IZ=n3NQi6pcW7gjAG`Rg{zzg_M+y eL3~9eA+WfTpo)?cNC0FYNIQtFqy#b+!~y^b@D#8B 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 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg39MuT!+`?_82&T-$0|!mP)SLNNeK)T z1i+LMgCGM)L_v^&5hTJOD8MMlqy**)2q-Br3J5?17z7zXd?h6Y0YQ)kkP?Uqj9{*k Kk^+Q_l>`9f01~_#;E}= zVGITappUQ5YO1y7-T94LxhZt0W6Co7XSbN 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 zcmZ?rGn3)> zTx`0K)c@^b_}jA^sxG|hu|!ZE!~aHxf6Wa4+8L0|$D$Gv lzx81CEnxNC41fDkZHK5qCXv+RG!sKTQ7Vyj(SXgs005?LzTN-; 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 zcmZ?r85aLnpYzzI{S?SC zjsHO|>H5#m_n%?%fA0QQ#fy)^wJ=P3!?5B%!?OPpOFr7RoB_F{`9DKDI5Z~xXPEJy zVgCQ52^$gWXS`%s`=4R;fAJNcOdHRG)VKX-==sku@jpZVZ-%COMy>ndiv4;vGpzc} zu>L>8hW{$7UgFGKYc<*Gxu({{jB{%81a-?4>Z{u_oZ{~0#_XW0HfY~3}5 z!XpwzCt|y{F5i9}S>=C*{}Q!388(7dZu!r!`#;NuFK7zTxJtbzKqhbc&#>!1!|s2j sThF2?K;uex9A(({pJD5Nmi3>?x1B?i#pK#dJfybfRpgp0$Wjap00cv%z5oCK 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2qhW`xz|AR;z3h)RELIKDwkW~Uo qV8Do=m5r2*lo$n+V6>94u@Z={q-+GCp?sJ;j7BpD;s!>jb%FroW)sK& literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/scalelocky.bmp b/setup/data/tools/bitmaps/scalelocky.bmp new file mode 100644 index 0000000000000000000000000000000000000000..812394dbf423ea31e87bc98e26c2afcbc28d8400 GIT binary patch literal 238 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2qhW`xz|AR;z3h)RELIKDwkW~Uo pV8AG#q@--5q=b()HU`Tp8z~!s)EO%&86(qhb3hD6nE6oa1OeY@6Sn{W 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2qhW`xz|AR;z3h)RELIKDwkW~Uo pV8Do=m5q&+lo$n+l#P_Yw30Cz4Uzz>hpL0BM>7ZF21clLf&kSX6NUf) 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 zcmZ?rJ;4A0ehdr@8Vn2!0t^fc{0s~Xj0`Lg2?hp+YkUyQ#=yV;!UqlrO!OFk literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/select_mouserotate.bmp b/setup/data/tools/bitmaps/select_mouserotate.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0139fd55cf70da5ae777eaed7033ec3e729f2039 GIT binary patch literal 238 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Mbi3$b=h9i6s3>IKuU^sB#0K54)wCN3^6E{cM}#)5)^hJu11J;H(@J;op|NDO8y)EbaNK>+Yt8aDs{ literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/select_mousescale.bmp b/setup/data/tools/bitmaps/select_mousescale.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7cac288119fdae8795eca9c793c2c08a6bc4f11e GIT binary patch literal 238 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2qhW`u=4Gj$c|ASaK6yXpSgaRQY zB_%~cK|vv7V`Bvd1_ltVY;0_Rg*E|;D=8^Cn;07i3K$!kxS-N5P`!fdI0OI~ CWg7zk 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 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Mbi3$b=hAVs!43Y!k0|yQ;{Ac*j(9qDp z(7?dJ@c;jRuv%gXK`0P5HU_hVjSU3_g#`tLL0n-&L19A>-&jysP)JG17$hm=;slli k(T0M8AaP-kRxlw5)(qkrgB61e0+}QzCE+r4GjMo{xdW*G=O;| z69SA33<8V{jDkXff{KCyf)H8|#%B}+$qEVxDKQEP3MeTu3W8`s0dyK64^a=(k8U1F G7K8x>I}ZK; 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+Wef}q8Vn2!0t^fc{0s~Xj0`Mbi3$b=h9i6s3>E+r4Gaek9ANm*@E^=0 nmtYhG0|7<`0Rcuv1_4H}fPes)5)>q#4>J#JydcO+U?&0qF#--q 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg37ljD!+`?_82&T-$EgCBgt4Kqv9YnR xF@zQ{Ha0Y1Fg6C$3dRf|aRUWo26R4DoEc0rK=m^)fceHuN~knU9LXMGV*sSB82JDI 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg37ljD!+`?_82&T-$EgCBgrOh^7#kZJ z8-oc$0|sMbLuO+`0|hv3z-SES8!$q|5qzk)A&3U)0~x`2A3EZ7&I6d7#yIOk%0p&4kCoO85lrVoq>S?gh68e|Nm!TVEE40- zLE%4xfx&+U2Z#R*0RjIRA|n1XBqsi6C@lQX(AfB&VdBL93=08UD;{ENpBnOiWB{tgOl^*nO|2tgH%>VPaxpWMmWs5!~F2 zARx%Z2BH{wz%q=C5~>W!U;#!35XGR#D96Ac3t~zzFo4oOAEN*l8@I498yhztqaZgY z6C0x-7n=YhD-)v#kFp}8G=r*~9HW$+9D|&=xSX08qa1@Qqqv;16eC1G8zVOlBa5IQ z$ia-zN%3@L=cY-WsV33kxkYQw$V-OQ(P?nQoR0P}4%*ZXw z$b+PxiJ4DOgi(ZuP?ckp zQkG+sR+bfq>SttRWM$%EWE6zO9}}wxD0-Nfm<2(!9CG|AgW?ht0gQ|={cLP(Oiavd zY^*G7Ai&B5=CQDWvIeWNs-Tx|AQ3b!r;KeL*UW}6UUsRPmA)W*mLN`_2=jEtg;jErocFaV|(LW43L6O*7I3nLFVBO9}b zu%MJUqnZk%G=riXD7i~XF~})HvLe(7h&~tz)i1=uD9p{s!ovAy6^I$HW9KPy_{;1UXSmfLqPLz@R86#V9AIswl-MrX~j}Cgr3V89AAl zm;@R5*!e^lnK*@oxw*M<>IcUXqp}nuqYQ&8BO|!H2bH3bazvOB6se&21C`M*4va&LRva&L$GyxUqY|JcdOst^t1yVI((~oQxj15zVi-xJgMdQ*36UU_w zCJs}Fi-xJgMdQ*36UU_wCJs}Fi-xJgMdQ*36UU_wCJs}Fi-xIFRs~fKFdl-&r4J?! zQwd5}j8dRv%LpnmBpDeQ!BrJ6Sb~Qcp7ubc3mY3FI7=}h+Gn5!g&d=TvJ9gPsD_l6 zVU$({l^qzN2`O+HAkLJ|BBL0%sR8mlDC>gC4h2SK28e!8DT2_?Cm;-Jtf1(Z5(hQYK=vy`n;cSN;t~u{ z{Q`{4Ot7+pn~{x)m608+8xelejB-jcjLNX^Q)B>@9f}MLY@qNHKnp)^0dTPZG6EKU zs^XxCQUgUjXy?4E|6XjhUsTuP*zq|R#R10RtB{;R6u11 zsObu7lYq+*SlIy=0O>}!A7KtE3!(})iK-fthg&B^22%&B2t*Zb5>+)O54TQ;45kiL z5r`_>B&upm9&Viw8B86hA`n%$NmSLCJlr}VGMGA0MIfqhlc=gOdAM~#WEdC#f9R22 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2qhW`u<4F4G#8X6e>{|B*gD8wQx z2nGViU`h}~ySTWx2rvkWC@Cqq2nq^1DJiL_2nve0xVSihXeA{jqG?A#xG^C8ju1=0 FBmj)>7@hzC 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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

V0byP~i+&W(mF(#n#Qv&ehh|-p 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 N4|stAcup3Qq5zY@{NDfo 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 zcmZ?r9c3gUa(-n;>Al=tXQ#n_3Cx&)}}+ak zYHnd}Wo2b!V`FD$=jh<*?Ck97;u;$p+t=H-V#SKc$f&sZxRjKXjfc)6qd65R$jH^z zwW+Cj$s@Lrw4xZ78aJdxw+ll-L|$ij*bqlE-v`ho0*wqW@fgv zwOLtN+S=MWIy&N4@9N@`m6g@l)M#c7icMQPTm0&il9CDw3u|j@O-xM8&CRW?tnBP; z9UYxqUERFAy!`z9f`fy?BO+pBW0I1R($mv(b8?G{iptB&jf{*STC6}??3^5(+#p)~ c{epvoBO)W>Vq=q%k~7jXa&vQwi;5T+03S$PQUCw| literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/texture_lock.bmp b/setup/data/tools/bitmaps/texture_lock.bmp new file mode 100644 index 0000000000000000000000000000000000000000..bcc774245ffff30b744f4c24ba0d173ac0b6f779 GIT binary patch literal 774 zcmZ?rWn*Rl12YB&1`P%V1_1^J27U$x21W)6u=oWg1_mK+2nGu<{Ds=S?4IsZDdjX~rVLnI?J`7O_@&eq;a7)1=5Csq;AZ!o`lY)w4 zXh9c;DS(Q@wBxiFNdcx7OcA&_Ah$uJQAvn6fauNVF+9n0OMRn A8~^|S literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/textures_popup.bmp b/setup/data/tools/bitmaps/textures_popup.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5333382f9af910c054a511f319fe6e18159bdc3e GIT binary patch literal 238 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2s0|yxXGyG?0XlP*g|NlP&0|?_# zi!Q7L1`5VXN=nL1N+8!ffr21f*_l;P*jPzPIY>d+ INJ&Wv0CdzJNB{r; 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 zcmZ?rP(m zQ7-w{&hW2=;Xe`T|MoNd?PefG{hw(Je_If*A(xp`k`fN=gjwX{jIq78lnvkbsMedl*Q-#igV)EmTR_ z#ofI;EYwKBrKG&PEHuc#rM$enyflafOqUpegv-lal-$cf0!B*Z<>kd7Ys4Xx5&$j8 BFTnr+ 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2s0|yxXGyG?0XlP()U|?YQ|NlRT zVEB(iJ+iP82q+kVNF^mGt!!Xy3=&{qU|<5#U~$zTu)H#RXt0ukl9B>@S|~_P#l7q#icw9B%tC_UJjy7+{?>Lm6Vj+-OI~eKy*oYImj0AVvsH+B>=^AC4K+^ 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)xI|c>@4F(1V0R{#Jeg*~xMg~T(gbf1&!x26R1`9ASF#Kou&(HwlFfcH% tA7Ef$0_$O5VCZ0AU_ZgYz`()4ASlAXz@@~%zyiVy3=CWh3=D!u`T&4I2(ADC 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 zcmZ?rea8R+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2`uD)hW`u=3=9navB(i6B&Z|=RwpPZ zpa5bp3NkQ(Xa)ra5CRj7f(&3}n=*`>U^Jdja|rKG&P#E}82p9yX}8Uq0FA|Pe} 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+Wef}q8Vn2!0t^fc{0s~Xj0`Lg2^{2qhW`u=4Gj$c|NqCK0=KZBkP-+e r3W7)_B}GAHV`F1uBP5!^fPn#-&&+_zXJBA3K$b`6L(PMjF9-twDDfB( 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 ucmZ?r{lNeMW(*7r8Vn2!98k>2AOV&*#K6EH#0|k<0R{$!|3e%wFaQ8w&%#Rp 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 zcmZ?rwP9ia11|;!1`P%V26-rEWMBb{e_~`{5aNblRt5$J5C%&yG%y@EaDbtKfq~)w z|NjsnB$CAi1u&rVnUqifLf#3ik0idMiwgsbi=&{Rh>MGhlZy+4?*if>^But^Bjh0p zoIn~u`oa3Z>RsUGIU@1Fxk#bwrmJf%`|qMa0F?1;KZ8bODKg>=yys z4|6Zbogn{!XpsFNnt{ayq#x`*C?9M;SY8mKA0hy^55h%r4@f;of&o4JjZuLC8W)Xk IY{0+(0PH7iu>b%7 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 zcmZ?rwP0cZ12+Z+1`P%V26-rEWMBb{e_~`{5aNblkO&BaB^VkQ8WNma24;`~0}vmKVSGVhh!~77Y>bmHC@5%PjHKSs7^)UT zgY*j<8!0I%DZ$M*gz!NQVFt+?3xk~oatMeoC@5%bXl!f@Gpa;sWM_XfWRq%6EjQcX1Jc$h(MO(eDU0 z*~!rb%wS*vnF7);A_9_NV1bA`K`4-Zu!NHfm~vrYfoMe157Gv;4`x5eJg~3}1CtU8 z0K3-(Ngj)TK{`SH1<@$#MZo4e3c~yk@h{jPPA(!~h~$1p7e_~s`=Rn+X%Q587i9B5 a`oVmVwYdF*?p|Y5V1UL&;~N_=FaQ7=U~UTl literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/window4.bmp b/setup/data/tools/bitmaps/window4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..78563e1759a08e4abccb93042cd199a3bf9b5d79 GIT binary patch literal 572 zcmZ?rwP9ia11|;!1`P%V26-rEWMBb{e_~`{5aNblRt5$J5C%&yG%y@EaDbtKfq~)w z|NjsnB$CAi1u&rVnUqifRNe`ui5$L&i;I(spr9bg43K^>-x0)fae?z)M8EyZn#nHt@1i^O^5dn)Z zu(&u1g6xAh2c#avastVN%mm4U%meFp0oxCe2T4NtVEqU_hz0Twhz6Mlq7i(kdts5l QV2lb3(70%PV*>^T0Bb*Ol>h($ literal 0 HcmV?d00001 diff --git a/setup/data/tools/gl/lighting_DBS_XY_Z_arbfp1.cg b/setup/data/tools/gl/lighting_DBS_XY_Z_arbfp1.cg new file mode 100644 index 00000000..f535dbb2 --- /dev/null +++ b/setup/data/tools/gl/lighting_DBS_XY_Z_arbfp1.cg @@ -0,0 +1,92 @@ +/// ============================================================================ +/* +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. +*/ +/// ============================================================================ + +#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 zcmZ?rbzo!w13v}^1`P%V1_1^J27U$x21W)BusDbi;$~m~VKxQ^1`q~`{r~@;0R(Ls zY#H8&ykYn|>n{TnIshp|U?wI87BFB10|s1t1_lO@0+>7l3o9!~0Lo`zVFAgqvSQ=2 uf|Y_TVFAfw;j=QZvao<00+nZFWnchtVd_~~SyA+(@DZM2U|?WkU;qFlvJOiC literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_cleanup.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_cleanup.bmp new file mode 100644 index 0000000000000000000000000000000000000000..33cc9490f08d5987542b82cc6e8e51ee6d2d8f05 GIT binary patch literal 316 zcmZ?rwP9oc11|;!1`P%V1_1^J27U$x21W)BusDbi;$~m~VO9nP1`z(w@SoxFT4@H> zDf|ro|NjRm!HU5~Gl2kDnvoGCfXqebgOo8bFflQK8~|1g7D3}f6hKS^$rHzC0BHbO b2vQEU0AdBiJ_e8ikUW|LQMeF!u%!$De@_XE literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_dropent.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_dropent.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2fcdcba36568f7861194644c5084c40595258c04 GIT binary patch literal 312 zcmZ?rwP0ib12+Z+1`P%V1_1^J27U$x21W)Bu=pni1_mK+2xehmU;yF&4F4I9#*2dG z|Ns9Frm+x=P@|zzjEpD(ATE*s6BNJ|Gl3N_LCj!eWMpDsWMlw|fgH!k$N=IqF@QuM kTEO}tOfU(R2eUy0h!3J+7{X_QbHVzV;C!$xFi8v=05pmTG5`Po literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_merge.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_merge.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cc5e272cd28acabf840d47882aca3aa1f13e2bc7 GIT binary patch literal 312 zcmZ?rwP0ib12+Z+1`P%V1_1^J27U$x21W)Bu=pni1_mK+2xehmU;yF&4F4Gzz_=2u z21+n7K^ahr5o$C814IJKXJTMr1Peg;OpFW+j9>vUp9w@Vf(1YOsPcj0_+NC?8}B2t(C_ya2Kvq>uq529g6g48#UW!1xRh4op2r2^51=LYZI+i4Opk CQ3h`S literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_poly.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_poly.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ed720e90e2f1d8c01c3716d9cf691df751871adb GIT binary patch literal 320 zcmZ?rbzo!w13v}^1`P%V1_1^J27U$x21W)BusDbi;$~m~VKxQ^1`z(w@Soui!yks9 z3_ls{803>Tm}XR3qpcKAS?(; i0w1b@5o$gI3kwSq0|N^)R2=LCxNAV>f<&M+7775nv8Ri^OMS fU_@3A@(?l~>;!~)U@pjM5c{EAu#Z7bB90FLl~D$7 literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_trainpathplot.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_trainpathplot.bmp new file mode 100644 index 0000000000000000000000000000000000000000..66537dc1220871be9339eeaae1726ef84e5e0dd3 GIT binary patch literal 324 zcmZ?rbzx)xgCGV51`P%V1_1^J27U$x21W)Bu=pni1_mK+2xezsU;yF&4F4G-BBB}E z+S(W-BqSL4`9V4u82$28PsB1_nPr0R~}zOK}LWyFmUmJ)PSTJxVU)0inyRQ iK=}}Hum+GkSP6*7zyQ{T#D|E21Yj%(4OIu>AV>gPkQ(0r 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 zcmZ?r^wiLijx Svx1ml%nahAyB{orOaK7u4-I<& 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 zcmZ?reaFZE2LBis7&I6d7z7v?82A|&7#JBiz~Ubm7#M`OA=rq4fdPaW80r}q7#bKD z7+M$@7`hl380IoCFsx)?U|7$4Kms7XATPg|j-tE@h%bi*4E2?jwG^}!#ktr;M7hPq u4GkqEqznut#Thx7nOH!4bqzfQO<7?c4pCM?5FccUAyMuE*$q*RP!9kmrCQ+t literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level1.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..80a4b2996e9dbcf76cec18e761c746dfb15c3fb2 GIT binary patch literal 198 zcmZ?rJ;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K smf?-a8-~BL{(^+Tm{AZ61Q4_U0|TQVh)*FFU|2rjLNhRcl?Vz#`A~UA QC|>|BF9=cswvB-S05~EMi~s-t literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level4.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3783a80341059f6a66047506e4eb1719d1b06c20 GIT binary patch literal 198 zcmZ?rJ;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61dwR~MnNzil?EvXiGw8t7zG(X9Iz5FAt=ZQr3Ik0AOlz_ K#C(u$5C#A=91(s1 literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level5.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level5.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8f62851c5af230c3cbe61ba5279d7cd10a30775b GIT binary patch literal 198 zcmZ?rJ;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61Q4_U1B0NT0HYuyh=7PQfS7`Uf+#dho&lqad7S PU;rxt2@8Up3bqdbHVF}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;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61dwP320=jqMnM5aFd@hwD98ZfW6&^pn0k;w5G^3hf`U+S Lkgy=gsbKp6C=U@^ 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;ne5ehdr@8Vn2!0t^fc{0s~Xj0`Mbi3$b=1|e<;W@BJr0AY~C|Ns9PK+u-K zmf?-a8-~BL{(^+Tm{AZ61dwP3K|w(fpMeoVgVpj!3<3-c4EziX42%pMVDS$O3=Bft5NyoAzyQMa4E78SVC=xq z%HYJ%#o)@YmLZH`9YYwydWLX@tqf5N+Zm!6b~3~;>}QB)IKU9kaF8K^;V?rY!)b<8 zhO-Q547V9_8SXITF+5}_V0gk%$ncDzh~X_m8N*kGDu(|I^$bado(u&R!3+f!Aq-{K z5e(I~F$@h32@I`H$qbV`a~Wp%6f(^5D`oiaFrQ&nXg$N)@J5EM(d`V|V>%gj#&$F8 zjqhVPoH&W$aMC1(Q>oJ#E@#bSxR$+u;c4+|hHo`H82;4nW%%E)pFsi&jGzF@m4I@M z2=j&5dAWr7G&NOp6%37xBqX@G8JL&_rKDx$C3Qf2K{g&vRxvGYHC26WBO_%r0JcZn mK#8A21Z)+UFQ+KOD9(c5Yv^eRaEK!KMj&&+ToT*~Q4auv4QE&Y literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_stepon.bmp b/setup/data/tools/plugins/bitmaps/ufoai_stepon.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b73896fb33b9c59a10218f17683a2bfd4af40610 GIT binary patch literal 458 zcmZ?rJ;lfX2Dca(7&I6d7z7v?82A|&7#JBiz~Ubm7#M`OAy|`vfdPaWOu`xD?SmN@ z9I_Y~obwsvUE>)TTnZT&T#6VNT#FbK+>;m>+{+jkJgXQOylNO2ylWX4{F)dHgEAQy z0@@f9L(3T$LVFn$BkLI$;^#3iBrIUiPwiyTN$qBEO|N6n&z{8KSK7~DUonp%sA?*M zUF`~n@TU0;QEf{YVmena#CES@ukM^! zo-v3(fd&*nxgt=m2Bmy{Rwiz41}=6#`*^A5F0GU@$(%VC7N5z+ilV!P={wfx+}D1B2;x1{?261_sj` z3=C#B85qp&FxdH4GcZ^@WN-**U|_I(!QdF!%)nsvn!zckg@M8PJp+U7HwL$`ZU(pT zZUzRsp9~Cke;63-|1)?-Ph{|lp2XmrFpI%2aW+Fh@_dGvqU{WECA%3C%J(rORUT$Y zsX5NzkpG_{z2PiFM&o&g%;rlBSuIx>vRkh*WVc>pDC~K_P}uX3p=$bPhU%H$7#imP zXV8WMM<{@DwV_-`V)$ab(#)3nI`$wvAoc8uYAlWl{Gu?vrl7opqqU(qh;N1l^o+Po z3@p?&SQWK6x%h+_| Xu|XKb24N5zgh6Z&2C+dH#0Fsi_(Xk2 literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bt/bt-el1.txt b/setup/data/tools/plugins/bt/bt-el1.txt new file mode 100644 index 00000000..ccac5e6e --- /dev/null +++ b/setup/data/tools/plugins/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/setup/data/tools/plugins/bt/bt-el2.txt b/setup/data/tools/plugins/bt/bt-el2.txt new file mode 100644 index 00000000..e69de29b diff --git a/setup/data/tools/plugins/bt/door-tex-trim.txt b/setup/data/tools/plugins/bt/door-tex-trim.txt new file mode 100644 index 00000000..5211a98c --- /dev/null +++ b/setup/data/tools/plugins/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/setup/data/tools/plugins/bt/door-tex.txt b/setup/data/tools/plugins/bt/door-tex.txt new file mode 100644 index 00000000..94b352f3 --- /dev/null +++ b/setup/data/tools/plugins/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/setup/data/tools/plugins/bt/tp_ent.txt b/setup/data/tools/plugins/bt/tp_ent.txt new file mode 100644 index 00000000..09488da9 --- /dev/null +++ b/setup/data/tools/plugins/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/setup/data/tools/q3data.qdt b/setup/data/tools/q3data.qdt new file mode 100644 index 00000000..ba30ee12 --- /dev/null +++ b/setup/data/tools/q3data.qdt @@ -0,0 +1,552 @@ +$aseconvert models/mapobjects/spotlamp/spotlamp_x.ase +$exit + + +$aseconvert models/weapons2/machinegun/machinegun.ase -weapon +$aseconvert models/weapons2/bfg/bfg.ase -weapon +$aseconvert models/weapons2/railgun/railgun.ase -weapon +$aseconvert models/weapons2/shotgun/shotgun.ase -weapon + +$aseconvert models/weapons2/meshes/chaingun.ase -weapon +$aseconvert models/weapons2/meshes/flamethrower.ase -weapon +$aseconvert models/weapons2/meshes/grapple.ase -weapon +$aseconvert models/weapons2/meshes/grenadel.ase -weapon +$aseconvert models/weapons2/meshes/lightning.ase -weapon +$aseconvert models/weapons2/meshes/plasma.ase -weapon +$aseconvert models/weapons2/meshes/railgun.ase -weapon +$aseconvert models/weapons2/meshes/ricochet.ase -weapon +$aseconvert models/weapons2/meshes/rocketl.ase -weapon +$aseconvert models/weapons2/meshes/shotgun.ase -weapon + + +$aseconvert models/mapobjects/chain1.ase +$aseconvert models/mapobjects/chain2.ase +$aseconvert models/mapobjects/chain3.ase +$aseconvert models/mapobjects/chain4.ase + +$exit + +$aseanimconvert models/players/light/bandolier/bandolier.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/brandon/4brandon.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/cash/4cash.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/doom/doom.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/shauna/shauna.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/tim/4tim.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/visor/4visor.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/xian/4xian.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/carmack/carmack.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/heavy/paulj/4paulj.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/heavy/brock/brock.ase -origin 0 0 24 -playerparms 75 138 140 1 + +$exit + +// note: playerparms are "skipStart skipEnd maxUpperFrames maxHeadFrames" +$aseanimconvert models/players/medium/visor/4visor.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/visor/4visor.ase -lod 1 0 -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/visor/4visor.ase -lod 2 0 -origin 0 0 24 -playerparms 75 138 140 1 + + + +$aseconvert models/powerups/ammo/backpack.ase + +$aseconvert models/weapons2/meshes/assault.ase -weapon +$aseconvert models/weapons2/meshes/chaingun.ase -weapon +$aseconvert models/weapons2/meshes/flamethrower.ase -weapon +$aseconvert models/weapons2/meshes/grapple.ase -weapon +$aseconvert models/weapons2/meshes/grenadel.ase -weapon +$aseconvert models/weapons2/meshes/lightning.ase -weapon +$aseconvert models/weapons2/meshes/plasma.ase -weapon +$aseconvert models/weapons2/meshes/railgun.ase -weapon +$aseconvert models/weapons2/meshes/ricochet.ase -weapon +$aseconvert models/weapons2/meshes/rocketl.ase -weapon +$aseconvert models/weapons2/meshes/shotgun.ase -weapon + +$aseconvert models/mapobjects/arenalogo.ase + +// +// talk sprite +// +$modelname sprites/balloon +$cd sprites +$spritebase -8 -8 16 16 +$spriteshader sprites/balloon3.tga + +// plasma ball sprite +$modelname sprites/plasma +$cd sprites +$spritebase -16 -16 32 32 +$spriteshader sprites/plasma1.tga +$spriteshader sprites/plasma2.tga + +// +// player models +// note: playerparms are "skipStart skipEnd maxUpperFrames maxHeadFrames" +// +$aseanimconvert models/players/light/bandolier/bandolier.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/brandon/4brandon.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/cash/4cash.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/doom/doom.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/light/shauna/shauna.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/tim/4tim.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/visor/4visor.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/xian/4xian.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/medium/carmack/carmack.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/heavy/paulj/4paulj.ase -origin 0 0 24 -playerparms 75 138 140 1 +$aseanimconvert models/players/heavy/brock/brock.ase -origin 0 0 24 -playerparms 75 138 140 1 + +// +// weaphits +// +$modelname models/weaphits/bullet +$cd models/weaphits +$base bullet01.3ds +$frame bullet01.3ds +$frame bullet02.3ds +$frame bullet03.3ds +$frame bullet04.3ds +$frame bullet05.3ds +$frame bullet06.3ds +$skin bullet01.3ds +$skin bullet03.3ds +$skin bullet05.3ds + +$modelname models/weaphits/ring01 +$cd models/weaphits +$base ring01_1.3ds +$frame ring01_1.3ds +$frame ring01_2.3ds +$frame ring01_3.3ds +$frame ring01_4.3ds +$frame ring01_5.3ds +$frame ring01_6.3ds +$skin ring01_1.3ds +$skin ring01_3.3ds +$skin ring01_5.3ds + +$modelname models/weaphits/ring02 +$cd models/weaphits +$base ring02_1.3ds +$frame ring02_1.3ds +$frame ring02_2.3ds +$frame ring02_3.3ds +$frame ring02_4.3ds +$frame ring02_5.3ds +$frame ring02_6.3ds +$skin ring02_1.3ds +$skin ring02_3.3ds +$skin ring02_5.3ds + +$modelname models/weaphits/boomgra +$cd models/weaphits +$base boomgra1.3ds +$frame boomgra1.3ds +$frame boomgra2.3ds +$frame boomgra3.3ds +$frame boomgra4.3ds +$frame boomgra5.3ds +$frame boomgra6.3ds +$skin boomgra1.3ds +$skin boomgra3.3ds +$skin boomgra5.3ds + +$modelname models/weaphits/boom01 +$cd models/weaphits +$base boom01_1.3ds +$frame boom01_1.3ds +$frame boom01_2.3ds +$frame boom01_3.3ds +$frame boom01_4.3ds +$frame boom01_5.3ds +$frame boom01_6.3ds +$skin boom01_1.3ds +$skin boom01_3.3ds +$skin boom01_5.3ds + +$modelname models/weaphits/boom02 +$cd models/weaphits +$base boom02_1.3ds +$frame boom02_1.3ds +$frame boom02_2.3ds +$frame boom02_3.3ds +$frame boom02_4.3ds +$frame boom02_5.3ds +$frame boom02_6.3ds +$skin boom02_1.3ds +$skin boom02_3.3ds +$skin boom02_5.3ds + +$modelname models/weaphits/boom03 +$cd models/weaphits +$base boom03_1.3ds +$frame boom03_1.3ds +$frame boom03_2.3ds +$frame boom03_3.3ds +$frame boom03_4.3ds +$frame boom03_5.3ds +$frame boom03_6.3ds +$skin boom03_1.3ds +$skin boom03_3.3ds +$skin boom03_5.3ds + +$modelname models/weaphits/boom04 +$cd models/weaphits +$base boom04_1.3ds +$frame boom04_1.3ds +$frame boom04_2.3ds +$frame boom04_3.3ds +$frame boom04_4.3ds +$frame boom04_5.3ds +$frame boom04_6.3ds +$skin boom04_1.3ds +$skin boom04_3.3ds +$skin boom04_5.3ds + +$modelname models/weaphits/boom05 +$cd models/weaphits +$base boom05_1.3ds +$frame boom05_1.3ds +$frame boom05_2.3ds +$frame boom05_3.3ds +$frame boom05_4.3ds +$frame boom05_5.3ds +$frame boom05_6.3ds +$skin boom05_1.3ds +$skin boom05_3.3ds +$skin boom05_5.3ds + +// +// point powerups +// +$aseconvert models/powerups/points/small.ase +$aseconvert models/powerups/points/medium.ase +$aseconvert models/powerups/points/large.ase + +// +// ammo +// +$aseconvert models\powerups\ammo\bfgam.ASE +$aseconvert models\powerups\ammo\bfgam_1.ASE +$aseconvert models\powerups\ammo\bouncyam.ASE +$aseconvert models\powerups\ammo\bouncyam_1.ASE +$aseconvert models\powerups\ammo\carbineam.ASE +$aseconvert models\powerups\ammo\carbineam_1.ASE +$aseconvert models\powerups\ammo\flameam.ASE +$aseconvert models\powerups\ammo\flameam_1.ASE +$aseconvert models\powerups\ammo\grenadeam.ASE +$aseconvert models\powerups\ammo\grenadeam_1.ASE +$aseconvert models\powerups\ammo\lightningam.ASE +$aseconvert models\powerups\ammo\lightningam_1.ASE +$aseconvert models\powerups\ammo\machinegunam.ASE +$aseconvert models\powerups\ammo\machinegunam_1.ASE +$aseconvert models\powerups\ammo\plasmaam.ASE +$aseconvert models\powerups\ammo\plasmaam_1.ASE +$aseconvert models\powerups\ammo\railgunam.ASE +$aseconvert models\powerups\ammo\railgunam_1.ASE +$aseconvert models\powerups\ammo\rocketam.ASE +$aseconvert models\powerups\ammo\rocketam_1.ASE +$aseconvert models\powerups\ammo\shotgunam.ASE +$aseconvert models\powerups\ammo\shotgunam_1.ASE + +$aseconvert models/powerups/ammo/backpack.ase + +// +// missiles +// +$modelname models/ammo/plasma/plasma +$cd models/ammo/plasma +$base plasma1.3ds +$frame plasma1.3ds +$skin plasma1.3ds + +$modelname models/ammo/laser/laser +$cd models/ammo/laser +$base laser1.3ds +$frame laser1.3ds +$skin laser1.3ds + +$modelname models/ammo/rocket/rocket +$cd models/ammo/rocket +$base rocket1.3ds +$frame rocket1.3ds +$skin rocket1.3ds +$skin rocket2.3ds + +$aseconvert models/ammo/grenade1.ase +$aseconvert models/explosions/smoke2.ase + +// +// map objects +// +$aseconvert models/mapobjects/ceilinglamp1.ase +$aseconvert models/mapobjects/ceilinglamp2.ase +$aseconvert models/mapobjects/chain1.ase +$aseconvert models/mapobjects/chain2.ase +$aseconvert models/mapobjects/chain3.ase +$aseconvert models/mapobjects/chain4.ase +$aseconvert models/mapobjects/crux.ase +$aseconvert models/mapobjects/floorlamp1.ase +$aseconvert models/mapobjects/floorlamp2.ase +$aseconvert models/mapobjects/hook.ase +$aseconvert models/mapobjects/impale.ase +$aseconvert models/mapobjects/standinglamp1.ase +$aseconvert models/mapobjects/standinglamp2.ase +$aseconvert models/mapobjects/standinglamp3.ase +$aseconvert models/mapobjects/walllamp1.ase +$aseconvert models/mapobjects/walllamp2.ase +$aseconvert models/mapobjects/walllamp3.ase + + +// +// objects +// +$aseconvert models/objects/dmspot.ase -origin 0 0 24 +$aseconvert models/objects/box01.ase + +// +// weapons +// +$aseconvert models/weapons2/meshes/assault.ase -weapon +$aseconvert models/weapons2/meshes/bfg.ase -weapon +$aseconvert models/weapons2/meshes/chaingun.ase -weapon +$aseconvert models/weapons2/meshes/flamethrower.ase -weapon +$aseconvert models/weapons2/meshes/grapple.ase -weapon +$aseconvert models/weapons2/meshes/grenadel.ase -weapon +$aseconvert models/weapons2/meshes/lightning.ase -weapon +$aseconvert models/weapons2/meshes/plasma.ase -weapon +$aseconvert models/weapons2/meshes/railgun.ase -weapon +$aseconvert models/weapons2/meshes/ricochet.ase -weapon +$aseconvert models/weapons2/meshes/rocketl.ase -weapon +$aseconvert models/weapons2/meshes/shotgun.ase -weapon + +// +// blood +// +$modelname models/weaphits/blood +$cd models/weaphits +$spritebase -16 -16 32 32 +$spriteshader models/weaphits/blood201.tga +$spriteshader models/weaphits/blood202.tga +$spriteshader models/weaphits/blood203.tga +$spriteshader models/weaphits/blood204.tga +$spriteshader models/weaphits/blood205.tga + + +// +// missiles +// +$modelname models/ammo/plasma/plasma +$cd models/ammo/plasma +$base plasma1.3ds +$frame plasma1.3ds +$skin plasma1.3ds + +$modelname models/ammo/laser/laser +$cd models/ammo/laser +$base laser1.3ds +$frame laser1.3ds +$skin laser1.3ds + + +$modelname models/ammo/rocket/rocket +$cd models/ammo/rocket +$base rocket1.3ds +$frame rocket1.3ds +$skin rocket1.3ds +$skin rocket2.3ds + + +// weaphits + +$modelname models/weaphits/bullet +$cd models/weaphits +$base bullet01.3ds +$frame bullet01.3ds +$frame bullet02.3ds +$frame bullet03.3ds +$frame bullet04.3ds +$frame bullet05.3ds +$frame bullet06.3ds +$skin bullet01.3ds +$skin bullet03.3ds +$skin bullet05.3ds + +$modelname models/weaphits/ring01 +$cd models/weaphits +$base ring01_1.3ds +$frame ring01_1.3ds +$frame ring01_2.3ds +$frame ring01_3.3ds +$frame ring01_4.3ds +$frame ring01_5.3ds +$frame ring01_6.3ds +$skin ring01_1.3ds +$skin ring01_3.3ds +$skin ring01_5.3ds + +$modelname models/weaphits/ring02 +$cd models/weaphits +$base ring02_1.3ds +$frame ring02_1.3ds +$frame ring02_2.3ds +$frame ring02_3.3ds +$frame ring02_4.3ds +$frame ring02_5.3ds +$frame ring02_6.3ds +$skin ring02_1.3ds +$skin ring02_3.3ds +$skin ring02_5.3ds + +$modelname models/weaphits/boomgra +$cd models/weaphits +$base boomgra1.3ds +$frame boomgra1.3ds +$frame boomgra2.3ds +$frame boomgra3.3ds +$frame boomgra4.3ds +$frame boomgra5.3ds +$frame boomgra6.3ds +$skin boomgra1.3ds +$skin boomgra3.3ds +$skin boomgra5.3ds + +$modelname models/weaphits/boom01 +$cd models/weaphits +$base boom01_1.3ds +$frame boom01_1.3ds +$frame boom01_2.3ds +$frame boom01_3.3ds +$frame boom01_4.3ds +$frame boom01_5.3ds +$frame boom01_6.3ds +$skin boom01_1.3ds +$skin boom01_3.3ds +$skin boom01_5.3ds + +$modelname models/weaphits/boom02 +$cd models/weaphits +$base boom02_1.3ds +$frame boom02_1.3ds +$frame boom02_2.3ds +$frame boom02_3.3ds +$frame boom02_4.3ds +$frame boom02_5.3ds +$frame boom02_6.3ds +$skin boom02_1.3ds +$skin boom02_3.3ds +$skin boom02_5.3ds + +$modelname models/weaphits/boom03 +$cd models/weaphits +$base boom03_1.3ds +$frame boom03_1.3ds +$frame boom03_2.3ds +$frame boom03_3.3ds +$frame boom03_4.3ds +$frame boom03_5.3ds +$frame boom03_6.3ds +$skin boom03_1.3ds +$skin boom03_3.3ds +$skin boom03_5.3ds + +$modelname models/weaphits/boom04 +$cd models/weaphits +$base boom04_1.3ds +$frame boom04_1.3ds +$frame boom04_2.3ds +$frame boom04_3.3ds +$frame boom04_4.3ds +$frame boom04_5.3ds +$frame boom04_6.3ds +$skin boom04_1.3ds +$skin boom04_3.3ds +$skin boom04_5.3ds + +$modelname models/weaphits/boom05 +$cd models/weaphits +$base boom05_1.3ds +$frame boom05_1.3ds +$frame boom05_2.3ds +$frame boom05_3.3ds +$frame boom05_4.3ds +$frame boom05_5.3ds +$frame boom05_6.3ds +$skin boom05_1.3ds +$skin boom05_3.3ds +$skin boom05_5.3ds + + +// +// armor +// +$aseconvert models/powerups/armor/armor_red.ase +$aseconvert models/powerups/armor/armor_yel.ase +$aseconvert models/powerups/armor/armor_grn.ase +$aseconvert models/powerups/armor/shard.ase +$aseconvert models/powerups/armor/shard_sphere.ase +$aseconvert models/powerups/armor/shard_sphere_1.ase + +// +// health +// +$aseconvert models/powerups/health/small.ase +$aseconvert models/powerups/health/small_1.ase +$aseconvert models/powerups/health/medium.ase +$aseconvert models/powerups/health/medium_1.ase +$aseconvert models/powerups/health/large.ase +$aseconvert models/powerups/health/large_1.ase +$aseconvert models/powerups/health/mega.ase +$aseconvert models/powerups/health/mega_1.ase + +// +// holdable powerups +// +$aseconvert models/powerups/holdable/ringodeth.ase +$aseconvert models/powerups/holdable/medkit.ase +$aseconvert models/powerups/holdable/teleporter.ase + +// +// instant powerups +// +$aseconvert models/powerups/instant/enviro.ase +$aseconvert models/powerups/instant/enviro_1.ase +$aseconvert models/powerups/instant/enviro_ring.ase +$aseconvert models/powerups/instant/enviro_ring_1.ase +$aseconvert models/powerups/instant/flight.ase +$aseconvert models/powerups/instant/flight_1.ase +$aseconvert models/powerups/instant/flight_ring.ase +$aseconvert models/powerups/instant/flight_ring_1.ase +$aseconvert models/powerups/instant/haste.ase +$aseconvert models/powerups/instant/haste_1.ase +$aseconvert models/powerups/instant/haste_ring.ase +$aseconvert models/powerups/instant/haste_ring_1.ase +$aseconvert models/powerups/instant/invis.ase +$aseconvert models/powerups/instant/invis_1.ase +$aseconvert models/powerups/instant/invis_ring.ase +$aseconvert models/powerups/instant/invis_ring_1.ase +$aseconvert models/powerups/instant/scan.ase +$aseconvert models/powerups/instant/scan_1.ase +$aseconvert models/powerups/instant/scan_ring.ase +$aseconvert models/powerups/instant/scan_ring_1.ase +$aseconvert models/powerups/instant/quad.ase +$aseconvert models/powerups/instant/quad_1.ase +$aseconvert models/powerups/instant/quad_ring.ase +$aseconvert models/powerups/instant/quad_ring_1.ase +$aseconvert models/powerups/instant/regen.ase +$aseconvert models/powerups/instant/regen_1.ase +$aseconvert models/powerups/instant/regen_ring.ase +$aseconvert models/powerups/instant/regen_ring_1.ase + +$exit + +// +// maps +// +$maps test_box test_items mirror mirror2 + + +// +// misc +// +$file q3default.cfg + + diff --git a/setup/gpl.rtf b/setup/gpl.rtf new file mode 100644 index 00000000..3d8e489b --- /dev/null +++ b/setup/gpl.rtf @@ -0,0 +1,126 @@ +{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fnil\fprq1\fcharset0 Courier New;}{\f1\fswiss\fcharset0 Arial;}} {\*\generator Msftedit 5.41.15.1507;}\viewkind4\uc1\pard\lang2057\f0\fs16\pard\qc GNU GENERAL PUBLIC LICENSE\par +Version 2, June 1991\par +\pard\par +\pard\li100Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\par +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.\par +\pard\par +\pard\qc Preamble\par +\pard\par + 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.\par +\par + 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.\par +\par + 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.\par +\par + 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.\par +\par + 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.\par +\par + 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.\par +\par + 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.\par +\par + The precise terms and conditions for copying, distribution and modification follow.\par +\par +\pard\qc GNU GENERAL PUBLIC LICENSE\par +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\par +\pard\par + 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".\par +\par +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.\par +\par + 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.\par +\par +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.\par +\par + 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:\par +\par +\pard\li256a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.\par +\par +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.\par +\par +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.)\par +\pard\par +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.\par +\par +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.\par +\par +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.\par +\par + 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:\par +\par +\pard\li256a) 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,\par +\par +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,\par +\par +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.)\par +\pard\par +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.\par +\par +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.\par +\par + 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.\par +\par + 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.\par +\par + 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.\par +\par + 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.\par +\par +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.\par +\par +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.\par +\par +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.\par +\par + 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.\par +\par + 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.\par +\par +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.\par +\par + 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.\par +\par +\tab\tab\tab NO WARRANTY\par +\par + 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.\par +\par + 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.\par +\par +\pard\qc END OF TERMS AND CONDITIONS\par +\par +How to Apply These Terms to Your New Programs\par +\pard\par + 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.\par +\par + 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.\par +\par +\pard\li256\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={igIpa${DZ6%BEl5HQj3Z+^Yavp^$ZOX z6%>L}%d8X(jr5Hy^({>lGy?qG6$%ndk~0(x^^7zX6aq^Vb23XR6>JqOO?4E4bMx~{ zGBWeh6>Jp@xc=W}aAsg)1OX7q1OhC~Ad;1Zg_(tom5mLI**Q7b*g4tR*f_X3IJrQ8 zjf0zqn~Mj;0U5#w(!<2W%FN8l#m>eKQb8L2e}F-dgYf}V12dx%1Ct;lvmoRDBMkBk z42-NG*MY)<0Sp+Km|0la*f}`4xc?tv*ebxl#K_Fd#KO$V%EH3Hz*x)3#LU1V$SS00 n=*T7bFC}Pw&aUqAY)5e3MK^H%$7$+4qadL@?OGp9$fX6xv literal 0 HcmV?d00001 diff --git a/setup/linux/Help/Index.html b/setup/linux/Help/Index.html new file mode 100644 index 00000000..b4688ac2 --- /dev/null +++ b/setup/linux/Help/Index.html @@ -0,0 +1,34 @@ + + + +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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 ?z=dez9Ex5^IR@)KGAw8GZxZIo|mi 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

s6;0xNawc4le*p;l`0;{V_O|6kPQg9?W~ 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 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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 literal 0 HcmV?d00001 diff --git a/setup/linux/setup_image/setup.data/config.games.sh b/setup/linux/setup_image/setup.data/config.games.sh new file mode 100644 index 00000000..8a3d509e --- /dev/null +++ b/setup/linux/setup_image/setup.data/config.games.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Script for generating .game files + +# generate game support files +# Expects params (game, game path_prefix) + +exec >/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", +"

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

V0byP~i+&W(mF(#n#Qv&ehh|-p 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 N4|stAcup3Qq5zY@{NDfo 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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�(>S3l4F|Rgmj0fB!GsoV#30Mx)IqK)KJL* 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�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