From 600a90924e2ced94ca26f171087c0b1e1cf9a751 Mon Sep 17 00:00:00 2001 From: Marco Hladik Date: Tue, 17 Nov 2020 12:16:16 +0100 Subject: [PATCH] Initial commit --- CMakeLists.txt | 275 + LICENSE-GPL | 340 ++ LICENSE-LGPL | 36 + build_binarydist.sh | 2 + build_contents.txt | 150 + cmake/FindGLIB.cmake | 21 + cmake/FindGTK2.cmake | 21 + cmake/FindGTK3.cmake | 21 + cmake/FindGtkGLExt.cmake | 27 + cmake/FindMinizip.cmake | 21 + cmake/FindPango.cmake | 23 + cmake/scripts/package.cmake | 16 + compile_debug.sh | 1 + compile_release.sh | 1 + contrib/CMakeLists.txt | 16 + contrib/brushexport/CMakeLists.txt | 10 + contrib/brushexport/brushexport.def | 7 + contrib/brushexport/callbacks.cpp | 148 + contrib/brushexport/callbacks.h | 16 + contrib/brushexport/export.cpp | 365 ++ contrib/brushexport/export.h | 16 + contrib/brushexport/interface.cpp | 219 + contrib/brushexport/plugin.cpp | 135 + contrib/brushexport/plugin.h | 25 + contrib/brushexport/support.cpp | 32 + contrib/brushexport/support.h | 22 + contrib/prtview/AboutDialog.cpp | 99 + contrib/prtview/AboutDialog.h | 25 + contrib/prtview/CMakeLists.txt | 13 + contrib/prtview/ConfigDialog.cpp | 481 ++ contrib/prtview/ConfigDialog.h | 25 + contrib/prtview/LoadPortalFileDialog.cpp | 169 + contrib/prtview/LoadPortalFileDialog.h | 25 + contrib/prtview/PrtView.aps | Bin 0 -> 21916 bytes contrib/prtview/PrtView.def | 7 + contrib/prtview/PrtView.txt | 12 + contrib/prtview/portals.cpp | 639 +++ contrib/prtview/portals.h | 162 + contrib/prtview/prtview.cpp | 323 ++ contrib/prtview/prtview.h | 37 + contrib/prtview/res/PrtView.rc2 | 13 + icon.png | Bin 0 -> 65969 bytes include/CMakeLists.txt | 48 + include/aboutmsg.default | 1 + include/aboutmsg.h | 4 + include/crypt.h | 66 + include/cullable.h | 71 + include/defaults.h | 33 + include/dpkdeps.h | 91 + include/editable.h | 58 + include/iarchive.h | 163 + include/ibrush.h | 126 + include/icamera.h | 67 + include/idatastream.h | 83 + include/ieclass.h | 117 + include/ientity.h | 151 + include/ifilesystem.h | 134 + include/ifiletypes.h | 76 + include/ifilter.h | 89 + include/igl.h | 2816 ++++++++++ include/iglrender.h | 128 + include/igtkgl.h | 42 + include/iimage.h | 73 + include/imap.h | 83 + include/imodel.h | 50 + include/ioapi_buf.h | 52 + include/ioapi_mem.h | 52 + include/ipatch.h | 257 + include/iplugin.h | 53 + include/ireference.h | 77 + include/irender.h | 177 + include/iscenegraph.h | 215 + include/iscriplib.h | 86 + include/iselection.h | 145 + include/ishaders.h | 195 + include/itexdef.h | 52 + include/itextstream.h | 107 + include/itextures.h | 88 + include/itoolbar.h | 65 + include/iundo.h | 118 + include/mapfile.h | 72 + include/modelskin.h | 92 + include/moduleobserver.h | 33 + include/modulesystem.h | 233 + include/nameable.h | 41 + include/namespace.h | 62 + include/preferencesystem.cpp | 187 + include/preferencesystem.h | 67 + include/qerplugin.h | 168 + include/renderable.h | 74 + include/selectable.h | 272 + include/stream_version.h | 2 + include/unzip.h | 308 ++ include/version.h | 13 + include/warnings.h | 32 + include/windowobserver.h | 92 + include/zip.h | 212 + libs/CMakeLists.txt | 64 + libs/_.cpp | 1 + libs/archivelib.h | 224 + libs/bytebool.h | 32 + libs/bytestreamutils.h | 159 + libs/character.h | 44 + libs/cmdlib.h | 101 + libs/cmdlib/CMakeLists.txt | 3 + libs/cmdlib/cmdlib.cpp | 138 + libs/container/CMakeLists.txt | 8 + libs/container/array.cpp | 37 + libs/container/array.h | 170 + libs/container/cache.h | 178 + libs/container/container.h | 330 ++ libs/container/hashfunc.h | 402 ++ libs/container/hashtable.cpp | 63 + libs/container/hashtable.h | 410 ++ libs/container/stack.h | 211 + libs/convert.h | 266 + libs/ddslib.h | 250 + libs/ddslib/CMakeLists.txt | 3 + libs/ddslib/ddslib.c | 757 +++ libs/debugging/CMakeLists.txt | 3 + libs/debugging/debugging.cpp | 27 + libs/debugging/debugging.h | 129 + libs/dragplanes.h | 228 + libs/eclasslib.h | 306 ++ libs/entitylib.h | 686 +++ libs/entityxml.h | 98 + libs/etclib.c | 114 + libs/etclib.h | 33 + libs/etclib/CMakeLists.txt | 3 + libs/filematch.c | 74 + libs/filematch.h | 16 + libs/filematch/CMakeLists.txt | 3 + libs/fs_filesystem.h | 160 + libs/fs_path.h | 82 + libs/generic/CMakeLists.txt | 13 + libs/generic/arrayrange.h | 70 + libs/generic/bitfield.h | 115 + libs/generic/callback.cpp | 255 + libs/generic/callback.h | 349 ++ libs/generic/constant.cpp | 41 + libs/generic/constant.h | 50 + libs/generic/enumeration.h | 56 + libs/generic/functional.h | 210 + libs/generic/object.cpp | 39 + libs/generic/object.h | 90 + libs/generic/reference.h | 103 + libs/generic/referencecounted.h | 180 + libs/generic/static.cpp | 125 + libs/generic/static.h | 140 + libs/generic/vector.h | 212 + libs/globaldefs.h | 125 + libs/gtkutil/CMakeLists.txt | 37 + libs/gtkutil/accelerator.cpp | 609 +++ libs/gtkutil/accelerator.h | 164 + libs/gtkutil/button.cpp | 160 + libs/gtkutil/button.h | 52 + libs/gtkutil/clipboard.cpp | 155 + libs/gtkutil/clipboard.h | 37 + libs/gtkutil/closure.h | 77 + libs/gtkutil/container.h | 32 + libs/gtkutil/cursor.cpp | 131 + libs/gtkutil/cursor.h | 146 + libs/gtkutil/dialog.cpp | 293 ++ libs/gtkutil/dialog.h | 148 + libs/gtkutil/entry.cpp | 37 + libs/gtkutil/entry.h | 39 + libs/gtkutil/filechooser.cpp | 287 ++ libs/gtkutil/filechooser.h | 40 + libs/gtkutil/frame.cpp | 35 + libs/gtkutil/frame.h | 29 + libs/gtkutil/glfont.cpp | 362 ++ libs/gtkutil/glfont.h | 45 + libs/gtkutil/glwidget.cpp | 304 ++ libs/gtkutil/glwidget.h | 41 + libs/gtkutil/idledraw.h | 70 + libs/gtkutil/image.cpp | 82 + libs/gtkutil/image.h | 40 + libs/gtkutil/menu.cpp | 275 + libs/gtkutil/menu.h | 65 + libs/gtkutil/messagebox.cpp | 195 + libs/gtkutil/messagebox.h | 32 + libs/gtkutil/nonmodal.cpp | 112 + libs/gtkutil/nonmodal.h | 91 + libs/gtkutil/paned.cpp | 97 + libs/gtkutil/paned.h | 29 + libs/gtkutil/pointer.h | 40 + libs/gtkutil/toolbar.cpp | 79 + libs/gtkutil/toolbar.h | 44 + libs/gtkutil/widget.cpp | 86 + libs/gtkutil/widget.h | 117 + libs/gtkutil/window.cpp | 253 + libs/gtkutil/window.h | 105 + libs/gtkutil/xorrectangle.cpp | 50 + libs/gtkutil/xorrectangle.h | 88 + libs/imagelib.h | 134 + libs/instancelib.h | 168 + libs/l_net/CMakeLists.txt | 14 + libs/l_net/l_net.c | 581 +++ libs/l_net/l_net.h | 123 + libs/l_net/l_net_berkley.c | 748 +++ libs/l_net/l_net_wins.c | 761 +++ libs/l_net/l_net_wins.h | 54 + libs/maplib.h | 238 + libs/math/CMakeLists.txt | 12 + libs/math/_.cpp | 0 libs/math/aabb.h | 280 + libs/math/curve.h | 252 + libs/math/expression.cpp | 209 + libs/math/expression.h | 552 ++ libs/math/frustum.h | 607 +++ libs/math/line.h | 138 + libs/math/matrix.h | 1194 +++++ libs/math/pi.h | 43 + libs/math/plane.h | 136 + libs/math/quaternion.h | 301 ++ libs/math/vector.h | 709 +++ libs/mathlib.h | 483 ++ libs/mathlib/CMakeLists.txt | 7 + libs/mathlib/bbox.c | 456 ++ libs/mathlib/line.c | 43 + libs/mathlib/m4x4.c | 1848 +++++++ libs/mathlib/mathlib.c | 773 +++ libs/mathlib/ray.c | 143 + libs/md5lib.h | 91 + libs/md5lib/md5lib.c | 388 ++ libs/memory/CMakeLists.txt | 3 + libs/memory/allocator.cpp | 76 + libs/memory/allocator.h | 291 ++ libs/moduleobservers.h | 59 + libs/modulesystem/CMakeLists.txt | 5 + libs/modulesystem/moduleregistry.h | 62 + libs/modulesystem/modulesmap.h | 135 + libs/modulesystem/singletonmodule.cpp | 46 + libs/modulesystem/singletonmodule.h | 131 + libs/os/CMakeLists.txt | 10 + libs/os/_.cpp | 0 libs/os/dir.h | 68 + libs/os/file.h | 139 + libs/os/path.h | 255 + libs/picomodel.h | 351 ++ libs/picomodel/CMakeLists.txt | 27 + libs/picomodel/lwo/clip.c | 300 ++ libs/picomodel/lwo/envelope.c | 641 +++ libs/picomodel/lwo/list.c | 100 + libs/picomodel/lwo/lwio.c | 472 ++ libs/picomodel/lwo/lwo2.c | 365 ++ libs/picomodel/lwo/lwo2.h | 653 +++ libs/picomodel/lwo/lwob.c | 871 ++++ libs/picomodel/lwo/pntspols.c | 588 +++ libs/picomodel/lwo/surface.c | 1108 ++++ libs/picomodel/lwo/vecmath.c | 34 + libs/picomodel/lwo/vmap.c | 266 + libs/picomodel/picointernal.c | 1349 +++++ libs/picomodel/picointernal.h | 208 + libs/picomodel/picomodel.c | 2317 +++++++++ libs/picomodel/picomodules.c | 89 + libs/picomodel/pm_3ds.c | 774 +++ libs/picomodel/pm_ase.c | 1181 +++++ libs/picomodel/pm_fm.c | 662 +++ libs/picomodel/pm_fm.h | 367 ++ libs/picomodel/pm_iqm.c | 370 ++ libs/picomodel/pm_lwo.c | 420 ++ libs/picomodel/pm_md2.c | 659 +++ libs/picomodel/pm_md3.c | 418 ++ libs/picomodel/pm_mdc.c | 742 +++ libs/picomodel/pm_ms3d.c | 496 ++ libs/picomodel/pm_obj.c | 946 ++++ libs/picomodel/pm_terrain.c | 594 +++ libs/pivot.h | 284 + libs/profile/CMakeLists.txt | 4 + libs/profile/file.cpp | 376 ++ libs/profile/file.h | 166 + libs/profile/profile.cpp | 292 ++ libs/profile/profile.h | 49 + libs/property.h | 167 + libs/render.h | 1209 +++++ libs/scenelib.h | 965 ++++ libs/script/CMakeLists.txt | 5 + libs/script/_.cpp | 0 libs/script/scripttokeniser.h | 343 ++ libs/script/scripttokenwriter.h | 77 + libs/selectionlib.h | 179 + libs/shaderlib.h | 84 + libs/signal/CMakeLists.txt | 5 + libs/signal/isignal.h | 166 + libs/signal/signal.cpp | 96 + libs/signal/signal.h | 342 ++ libs/signal/signalfwd.h | 44 + libs/splines/CMakeLists.txt | 11 + libs/splines/math_angles.cpp | 152 + libs/splines/math_angles.h | 195 + libs/splines/math_matrix.cpp | 134 + libs/splines/math_matrix.h | 215 + libs/splines/math_quaternion.cpp | 79 + libs/splines/math_quaternion.h | 190 + libs/splines/math_vector.cpp | 147 + libs/splines/math_vector.h | 576 +++ libs/splines/q_parse.cpp | 537 ++ libs/splines/q_shared.cpp | 999 ++++ libs/splines/q_shared.h | 803 +++ libs/splines/splines.cpp | 1449 ++++++ libs/splines/splines.h | 1129 ++++ libs/splines/util_list.h | 347 ++ libs/splines/util_str.cpp | 577 +++ libs/splines/util_str.h | 721 +++ libs/str.h | 472 ++ libs/stream/CMakeLists.txt | 8 + libs/stream/_.cpp | 0 libs/stream/filestream.h | 173 + libs/stream/memstream.h | 74 + libs/stream/stringstream.h | 148 + libs/stream/textfilestream.h | 80 + libs/stream/textstream.h | 475 ++ libs/string/CMakeLists.txt | 5 + libs/string/pooledstring.cpp | 25 + libs/string/pooledstring.h | 93 + libs/string/string.h | 548 ++ libs/string/stringfwd.h | 35 + libs/stringio.h | 374 ++ libs/texturelib.h | 43 + libs/transformlib.h | 176 + libs/traverselib.h | 350 ++ libs/typesystem.h | 137 + libs/uilib/CMakeLists.txt | 18 + libs/uilib/uilib.cpp | 473 ++ libs/uilib/uilib.h | 643 +++ libs/undolib.h | 141 + libs/uniquenames.h | 322 ++ libs/versionlib.h | 84 + libs/xml/CMakeLists.txt | 15 + libs/xml/ixml.h | 60 + libs/xml/xmlelement.h | 99 + libs/xml/xmlparser.h | 220 + libs/xml/xmltextags.cpp | 592 +++ libs/xml/xmltextags.h | 104 + libs/xml/xmlwriter.h | 178 + plugins/CMakeLists.txt | 22 + plugins/archivezip/CMakeLists.txt | 10 + plugins/archivezip/archive.cpp | 343 ++ plugins/archivezip/archive.h | 22 + plugins/archivezip/archivezip.def | 7 + plugins/archivezip/pkzip.h | 258 + plugins/archivezip/plugin.cpp | 106 + plugins/archivezip/zlibstream.h | 71 + plugins/config.mk | 32 + plugins/entity/CMakeLists.txt | 29 + plugins/entity/angle.h | 98 + plugins/entity/angles.h | 133 + plugins/entity/colour.h | 126 + plugins/entity/curve.h | 452 ++ plugins/entity/doom3group.cpp | 854 +++ plugins/entity/doom3group.h | 36 + plugins/entity/eclassmodel.cpp | 542 ++ plugins/entity/eclassmodel.h | 34 + plugins/entity/entity.cpp | 395 ++ plugins/entity/entity.h | 48 + plugins/entity/entityq3.def | 7 + plugins/entity/filters.cpp | 71 + plugins/entity/filters.h | 82 + plugins/entity/generic.cpp | 532 ++ plugins/entity/generic.h | 27 + plugins/entity/group.cpp | 535 ++ plugins/entity/group.h | 27 + plugins/entity/keyobservers.h | 54 + plugins/entity/light.cpp | 1680 ++++++ plugins/entity/light.h | 36 + plugins/entity/miscmodel.cpp | 513 ++ plugins/entity/miscmodel.h | 31 + plugins/entity/model.h | 126 + plugins/entity/modelskinkey.h | 115 + plugins/entity/namedentity.h | 111 + plugins/entity/namekeys.h | 156 + plugins/entity/origin.h | 167 + plugins/entity/plugin.cpp | 96 + plugins/entity/prop_dynamic.cpp | 507 ++ plugins/entity/prop_dynamic.h | 31 + plugins/entity/rotation.h | 192 + plugins/entity/scale.h | 124 + plugins/entity/skincache.cpp | 335 ++ plugins/entity/skincache.h | 29 + plugins/entity/targetable.cpp | 38 + plugins/entity/targetable.h | 451 ++ plugins/image/CMakeLists.txt | 13 + plugins/image/bmp.cpp | 197 + plugins/image/bmp.h | 31 + plugins/image/dds.cpp | 55 + plugins/image/dds.h | 31 + plugins/image/image.cpp | 186 + plugins/image/imageq3.def | 7 + plugins/image/jpeg.cpp | 403 ++ plugins/image/jpeg.h | 40 + plugins/image/ktx.cpp | 431 ++ plugins/image/ktx.h | 31 + plugins/image/pcx.cpp | 211 + plugins/image/pcx.h | 31 + plugins/image/tga.cpp | 433 ++ plugins/image/tga.h | 31 + plugins/iqmmodel/CMakeLists.txt | 4 + plugins/iqmmodel/iqm.cpp | 313 ++ plugins/iqmmodel/iqm.h | 31 + plugins/iqmmodel/modeliqm.def | 7 + plugins/iqmmodel/plugin.cpp | 101 + plugins/iqmmodel/plugin.h | 25 + plugins/mapq3/CMakeLists.txt | 9 + plugins/mapq3/mapq3.def | 7 + plugins/mapq3/parse.cpp | 139 + plugins/mapq3/parse.h | 48 + plugins/mapq3/plugin.cpp | 146 + plugins/mapq3/write.cpp | 118 + plugins/mapq3/write.h | 29 + plugins/md3model/model.h | 634 +++ plugins/model/CMakeLists.txt | 7 + plugins/model/bitmaps/model_reload_entity.bmp | Bin 0 -> 308 bytes plugins/model/bitmaps/picomodel.bmp | Bin 0 -> 192660 bytes plugins/model/model.cpp | 1024 ++++ plugins/model/model.h | 32 + plugins/model/modelpico.def | 7 + plugins/model/plugin.cpp | 188 + plugins/shaders/CMakeLists.txt | 8 + plugins/shaders/plugin.cpp | 86 + plugins/shaders/shaders.cpp | 1566 ++++++ plugins/shaders/shaders.h | 43 + plugins/shaders/shadersq3.def | 7 + plugins/vfspk3/CMakeLists.txt | 12 + plugins/vfspk3/archive.cpp | 169 + plugins/vfspk3/archive.h | 29 + plugins/vfspk3/vfs.cpp | 990 ++++ plugins/vfspk3/vfs.h | 42 + plugins/vfspk3/vfspk3.cpp | 84 + plugins/vfspk3/vfsq3.def | 7 + radiant/CMakeLists.txt | 133 + radiant/autosave.cpp | 210 + radiant/autosave.h | 38 + radiant/brush.cpp | 394 ++ radiant/brush.h | 4064 +++++++++++++++ radiant/brush_primit.cpp | 1401 +++++ radiant/brush_primit.h | 163 + radiant/brushmanip.cpp | 1372 +++++ radiant/brushmanip.h | 111 + radiant/brushmodule.cpp | 485 ++ radiant/brushmodule.h | 36 + radiant/brushnode.cpp | 22 + radiant/brushnode.h | 182 + radiant/brushtokens.cpp | 22 + radiant/brushtokens.h | 755 +++ radiant/brushxml.cpp | 22 + radiant/brushxml.h | 434 ++ radiant/build.cpp | 1104 ++++ radiant/build.h | 48 + radiant/camwindow.cpp | 2204 ++++++++ radiant/camwindow.h | 89 + radiant/clippertool.cpp | 22 + radiant/clippertool.h | 25 + radiant/commands.cpp | 634 +++ radiant/commands.h | 62 + radiant/console.cpp | 254 + radiant/console.h | 50 + radiant/csg.cpp | 653 +++ radiant/csg.h | 54 + radiant/dialog.cpp | 716 +++ radiant/dialog.h | 194 + radiant/eclass.cpp | 399 ++ radiant/eclass.h | 25 + radiant/eclass_def.cpp | 378 ++ radiant/eclass_def.h | 25 + radiant/eclass_doom3.cpp | 827 +++ radiant/eclass_doom3.h | 25 + radiant/eclass_fgd.cpp | 697 +++ radiant/eclass_fgd.h | 25 + radiant/eclass_xml.cpp | 598 +++ radiant/eclass_xml.h | 25 + radiant/entity.cpp | 637 +++ radiant/entity.h | 47 + radiant/entityinspector.cpp | 1756 +++++++ radiant/entityinspector.h | 35 + radiant/entitylist.cpp | 417 ++ radiant/entitylist.h | 37 + radiant/environment.cpp | 286 ++ radiant/environment.h | 34 + radiant/error.cpp | 139 + radiant/error.h | 27 + radiant/feedback.cpp | 334 ++ radiant/feedback.h | 241 + radiant/filetypes.cpp | 153 + radiant/filetypes.h | 31 + radiant/filters.cpp | 307 ++ radiant/filters.h | 29 + radiant/findtexturedialog.cpp | 284 + radiant/findtexturedialog.h | 39 + radiant/glwidget.cpp | 55 + radiant/glwidget.h | 25 + radiant/grid.cpp | 302 ++ radiant/grid.h | 46 + radiant/groupdialog.cpp | 232 + radiant/groupdialog.h | 59 + radiant/gtkdlgs.cpp | 1024 ++++ radiant/gtkdlgs.h | 68 + radiant/gtkmisc.cpp | 163 + radiant/gtkmisc.h | 71 + radiant/help.cpp | 132 + radiant/help.h | 29 + radiant/image.cpp | 68 + radiant/image.h | 29 + radiant/main.cpp | 701 +++ radiant/main.h | 25 + radiant/mainframe.cpp | 3587 +++++++++++++ radiant/mainframe.h | 332 ++ radiant/map.cpp | 2380 +++++++++ radiant/map.h | 191 + radiant/mru.cpp | 248 + radiant/mru.h | 35 + radiant/multimon.cpp | 104 + radiant/multimon.h | 57 + radiant/nullmodel.cpp | 219 + radiant/nullmodel.h | 39 + radiant/parse.cpp | 52 + radiant/parse.h | 25 + radiant/patch.cpp | 2957 +++++++++++ radiant/patch.h | 2128 ++++++++ radiant/patchdialog.cpp | 1235 +++++ radiant/patchdialog.h | 46 + radiant/patchmanip.cpp | 1137 ++++ radiant/patchmanip.h | 78 + radiant/patchmodule.cpp | 247 + radiant/patchmodule.h | 25 + radiant/plugin.cpp | 356 ++ radiant/plugin.h | 57 + radiant/pluginapi.cpp | 91 + radiant/pluginapi.h | 37 + radiant/pluginmanager.cpp | 253 + radiant/pluginmanager.h | 73 + radiant/pluginmenu.cpp | 181 + radiant/pluginmenu.h | 33 + radiant/plugintoolbar.cpp | 154 + radiant/plugintoolbar.h | 35 + radiant/points.cpp | 414 ++ radiant/points.h | 43 + radiant/preferencedictionary.cpp | 22 + radiant/preferencedictionary.h | 280 + radiant/preferences.cpp | 1035 ++++ radiant/preferences.h | 467 ++ radiant/qe3.cpp | 395 ++ radiant/qe3.h | 67 + radiant/qgl.cpp | 1516 ++++++ radiant/qgl.h | 35 + radiant/referencecache.cpp | 812 +++ radiant/referencecache.h | 46 + radiant/renderer.cpp | 22 + radiant/renderer.h | 188 + radiant/renderstate.cpp | 2522 +++++++++ radiant/renderstate.h | 29 + radiant/resource.h | 18 + radiant/scenegraph.cpp | 296 ++ radiant/scenegraph.h | 25 + radiant/select.cpp | 1245 +++++ radiant/select.h | 111 + radiant/selection.cpp | 4092 +++++++++++++++ radiant/selection.h | 55 + radiant/server.cpp | 282 + radiant/server.h | 35 + radiant/shaders.cpp | 71 + radiant/shaders.h | 27 + radiant/sockets.cpp | 47 + radiant/sockets.h | 14 + radiant/stacktrace.cpp | 305 ++ radiant/stacktrace.h | 29 + radiant/surfacedialog.cpp | 2326 +++++++++ radiant/surfacedialog.h | 65 + radiant/texmanip.cpp | 343 ++ radiant/texmanip.h | 42 + radiant/textureentry.cpp | 72 + radiant/textureentry.h | 95 + radiant/textures.cpp | 901 ++++ radiant/textures.h | 35 + radiant/texwindow.cpp | 2973 +++++++++++ radiant/texwindow.h | 68 + radiant/timer.cpp | 103 + radiant/timer.h | 101 + radiant/treemodel.cpp | 1488 ++++++ radiant/treemodel.h | 39 + radiant/undo.cpp | 601 +++ radiant/undo.h | 25 + radiant/url.cpp | 67 + radiant/url.h | 27 + radiant/view.cpp | 58 + radiant/view.h | 223 + radiant/watchbsp.cpp | 819 +++ radiant/watchbsp.h | 45 + radiant/winding.cpp | 329 ++ radiant/winding.h | 329 ++ radiant/windowobservers.cpp | 174 + radiant/windowobservers.h | 60 + radiant/worldspawn.ico | Bin 0 -> 5430 bytes radiant/worldspawn.rc | 74 + radiant/xmlstuff.cpp | 31 + radiant/xmlstuff.h | 95 + radiant/xywindow.cpp | 2998 +++++++++++ radiant/xywindow.h | 366 ++ .../q3map2/base_winding/README.txt | 8 + .../base_winding/base_winding_logging.patch | 27 + .../q3map2/base_winding/maps/base_winding.map | 1284 +++++ .../q3map2/coarse_snap_normal/README.txt | 16 + .../maps/coarse_snap_normal.map | 84 + .../q3map2/decal_misalignment/README.txt | 16 + .../maps/decal_misalignment.map | 89 + .../scripts/radiant_regression_tests.shader | 29 + .../q3map2/degenerate_winding/README.txt | 4 + .../maps/degenerate_winding.map | 68 + .../q3map2/degenerate_winding2/README.txt | 14 + .../maps/degenerate_winding2.map | 69 + .../q3map2/degenerate_winding3/README.txt | 18 + .../maps/degenerate_winding3.map | 68 + .../q3map2/disappearing_sliver/README.txt | 183 + .../maps/disappearing_sliver.map | 73 + .../disappearing_sliver/winding_logging.patch | 105 + .../q3map2/disappearing_sliver2/README.txt | 93 + .../maps/disappearing_sliver2.map | 69 + .../q3map2/disappearing_sliver3/NOTES.txt | 72 + .../q3map2/disappearing_sliver3/README.txt | 49 + .../maps/disappearing_sliver3.map | 68 + .../q3map2/duplicate_plane/README.txt | 13 + .../duplicate_plane/maps/duplicate_plane.map | 67 + .../model_clipping_45_degrees/README.txt | 7 + .../maps/model_clipping_45_degrees.map | 81 + .../models/mapobjects/wedges/wedge_45.ase | 368 ++ .../mapobjects/wedges/wedge_shallow.ase | 368 ++ .../models/mapobjects/wedges/wedge_steep.ase | 368 ++ .../scripts/radiant_regression_tests.shader | 12 + regression_tests/q3map2/patch_seam/README.txt | 8 + .../q3map2/patch_seam/maps/patch_seam.map | 85 + .../radiant_regression_tests/green.tga | Bin 0 -> 300 bytes .../radiant_regression_tests/tile.tga | Bin 0 -> 812 bytes .../q3map2/piercing_triangle/README.txt | 14 + .../maps/piercing_triangle.map | 73 + .../q3map2/plane_aliasing/README.txt | 129 + .../plane_aliasing/maps/plane_aliasing.map | 74 + .../q3map2/segmentation_fault/README.txt | 27 + .../maps/segmentation_fault.map | 69 + regression_tests/q3map2/snap_plane/README.txt | 45 + .../q3map2/snap_plane/maps/snap_plane.map | 78 + .../q3map2/sparkly_seam/README.txt | 21 + .../q3map2/sparkly_seam/maps/sparkly_seam.map | 75 + resources/Resources/FileIcon_.map.tiff | Bin 0 -> 9537 bytes resources/Resources/Info-gnustep.plist | 16 + resources/Resources/icon.tiff | Bin 0 -> 9542 bytes resources/WorldSpawn | 4 + resources/bitmaps/black.xpm | 16 + resources/bitmaps/brush_flipx.xpm | 25 + resources/bitmaps/brush_flipy.xpm | 25 + resources/bitmaps/brush_flipz.xpm | 25 + resources/bitmaps/brush_rotatex.xpm | 25 + resources/bitmaps/brush_rotatey.xpm | 25 + resources/bitmaps/brush_rotatez.xpm | 25 + resources/bitmaps/cap_bevel.xpm | 33 + resources/bitmaps/cap_curve.xpm | 25 + resources/bitmaps/cap_cylinder.xpm | 33 + resources/bitmaps/cap_endcap.xpm | 33 + resources/bitmaps/cap_ibevel.xpm | 33 + resources/bitmaps/cap_iendcap.xpm | 33 + resources/bitmaps/console.xpm | 25 + resources/bitmaps/copy.xpm | 29 + resources/bitmaps/cut.xpm | 28 + resources/bitmaps/dontselectcurve.xpm | 24 + resources/bitmaps/dontselectmodel.xpm | 24 + resources/bitmaps/ellipsis.xpm | 12 + resources/bitmaps/entities.xpm | 25 + resources/bitmaps/file_new.xpm | 25 + resources/bitmaps/file_open.xpm | 27 + resources/bitmaps/file_save.xpm | 26 + resources/bitmaps/icon.xpm | 271 + resources/bitmaps/lightinspector.xpm | 192 + resources/bitmaps/logo.xpm | 148 + resources/bitmaps/modify_edges.xpm | 25 + resources/bitmaps/modify_faces.xpm | 25 + resources/bitmaps/modify_vertices.xpm | 25 + resources/bitmaps/noFalloff.xpm | 15 + resources/bitmaps/notex.tga | Bin 0 -> 5036 bytes resources/bitmaps/paste.xpm | 33 + resources/bitmaps/patch_bend.xpm | 25 + resources/bitmaps/patch_drilldown.xpm | 25 + resources/bitmaps/patch_insdel.xpm | 25 + resources/bitmaps/patch_showboundingbox.xpm | 25 + resources/bitmaps/patch_weld.xpm | 26 + resources/bitmaps/patch_wireframe.xpm | 24 + resources/bitmaps/popup_selection.xpm | 28 + resources/bitmaps/redo.xpm | 24 + resources/bitmaps/refresh_models.xpm | 25 + resources/bitmaps/scalelockx.xpm | 25 + resources/bitmaps/scalelocky.xpm | 25 + resources/bitmaps/scalelockz.xpm | 25 + resources/bitmaps/select_mouseresize.xpm | 24 + resources/bitmaps/select_mouserotate.xpm | 25 + resources/bitmaps/select_mousescale.xpm | 25 + resources/bitmaps/select_mousetranslate.xpm | 25 + resources/bitmaps/selection_csgmerge.xpm | 25 + resources/bitmaps/selection_csgsubtract.xpm | 25 + resources/bitmaps/selection_makehollow.xpm | 25 + resources/bitmaps/selection_makeroom.xpm | 25 + .../bitmaps/selection_selectcompletetall.xpm | 24 + resources/bitmaps/selection_selectinside.xpm | 25 + .../bitmaps/selection_selectpartialtall.xpm | 25 + .../bitmaps/selection_selecttouching.xpm | 25 + resources/bitmaps/shadernotex.tga | Bin 0 -> 4494 bytes resources/bitmaps/show_entities.xpm | 26 + resources/bitmaps/side_clipper.xpm | 45 + resources/bitmaps/side_edges.xpm | 45 + resources/bitmaps/side_entities.xpm | 45 + resources/bitmaps/side_faces.xpm | 45 + resources/bitmaps/side_find.xpm | 44 + resources/bitmaps/side_patch.xpm | 157 + resources/bitmaps/side_resize.xpm | 43 + resources/bitmaps/side_rotate.xpm | 44 + resources/bitmaps/side_scale.xpm | 44 + resources/bitmaps/side_surface.xpm | 48 + resources/bitmaps/side_textures.xpm | 148 + resources/bitmaps/side_transform.xpm | 44 + resources/bitmaps/side_vertices.xpm | 44 + resources/bitmaps/splash.xcf | Bin 0 -> 391211 bytes resources/bitmaps/splash.xpm | 450 ++ resources/bitmaps/texture_browser.xpm | 26 + resources/bitmaps/texture_lock.xpm | 27 + resources/bitmaps/textures_popup.xpm | 27 + resources/bitmaps/undo.xpm | 24 + resources/bitmaps/view_cameratoggle.xpm | 29 + resources/bitmaps/view_cameraupdate.xpm | 29 + resources/bitmaps/view_change.xpm | 24 + resources/bitmaps/view_clipper.xpm | 25 + resources/bitmaps/view_cubicclipping.xpm | 25 + resources/bitmaps/view_entity.xpm | 26 + resources/bitmaps/white.xpm | 15 + resources/bitmaps/window1.xpm | 43 + resources/bitmaps/window2.xpm | 42 + resources/bitmaps/window3.xpm | 43 + resources/bitmaps/window4.xpm | 43 + resources/games/platform.game | 27 + resources/gl/lighting_DBS_XY_Z_arbfp1.cg | 92 + resources/gl/lighting_DBS_XY_Z_arbvp1.cg | 78 + resources/gl/lighting_DBS_omni_fp.glp | 86 + resources/gl/lighting_DBS_omni_fp.glsl | 73 + resources/gl/lighting_DBS_omni_vp.glp | 410 ++ resources/gl/lighting_DBS_omni_vp.glsl | 58 + resources/gl/utils.cg | 36 + resources/gl/zfill_arbfp1.cg | 47 + resources/gl/zfill_arbvp1.cg | 49 + resources/gl/zfill_fp.glp | 19 + resources/gl/zfill_fp.glsl | 29 + resources/gl/zfill_vp.glp | 384 ++ resources/gl/zfill_vp.glsl | 35 + resources/global.xlink | 5 + .../platform.game/default_build_menu.xml | 26 + resources/platform.game/platform/entities.def | 713 +++ tools/CMakeLists.txt | 120 + tools/common/aselib.c | 894 ++++ tools/common/aselib.h | 31 + tools/common/bspfile.c | 706 +++ tools/common/bspfile.h | 120 + tools/common/cmdlib.c | 1113 ++++ tools/common/cmdlib.h | 163 + tools/common/imagelib.c | 1534 ++++++ tools/common/imagelib.h | 47 + tools/common/inout.c | 364 ++ tools/common/inout.h | 63 + tools/common/jpeg.c | 385 ++ tools/common/l3dslib.c | 312 ++ tools/common/l3dslib.h | 25 + tools/common/md4.c | 219 + tools/common/md4.h | 56 + tools/common/mutex.c | 197 + tools/common/mutex.h | 28 + tools/common/polylib.c | 1147 +++++ tools/common/polylib.h | 76 + tools/common/polyset.h | 51 + tools/common/qfiles.h | 490 ++ tools/common/qthreads.h | 30 + tools/common/scriplib.c | 417 ++ tools/common/scriplib.h | 55 + tools/common/surfaceflags.h | 112 + tools/common/threads.c | 646 +++ tools/common/trilib.c | 237 + tools/common/trilib.h | 25 + tools/common/vfs.c | 425 ++ tools/common/vfs.h | 58 + tools/vmap/brush.c | 1127 ++++ tools/vmap/brush_primit.c | 83 + tools/vmap/bsp.c | 1033 ++++ tools/vmap/bsp_analyze.c | 195 + tools/vmap/bsp_info.c | 94 + tools/vmap/bsp_scale.c | 355 ++ tools/vmap/bspfile_abstract.c | 1114 ++++ tools/vmap/bspfile_ibsp.c | 574 +++ tools/vmap/bspfile_rbsp.c | 553 ++ tools/vmap/convert_ase.c | 441 ++ tools/vmap/convert_bsp.c | 273 + tools/vmap/convert_map.c | 886 ++++ tools/vmap/convert_obj.c | 326 ++ tools/vmap/decals.c | 902 ++++ tools/vmap/exportents.c | 112 + tools/vmap/facebsp.c | 513 ++ tools/vmap/fixaas.c | 110 + tools/vmap/fog.c | 888 ++++ tools/vmap/game__null.h | 94 + tools/vmap/game_fte.h | 216 + tools/vmap/help.c | 424 ++ tools/vmap/image.c | 491 ++ tools/vmap/leakfile.c | 124 + tools/vmap/light.c | 3155 ++++++++++++ tools/vmap/light_bounce.c | 982 ++++ tools/vmap/light_shadows.c | 128 + tools/vmap/light_trace.c | 1806 +++++++ tools/vmap/light_ydnar.c | 4576 +++++++++++++++++ tools/vmap/lightmaps.c | 115 + tools/vmap/lightmaps_ydnar.c | 3745 ++++++++++++++ tools/vmap/main.c | 270 + tools/vmap/map.c | 2025 ++++++++ tools/vmap/mesh.c | 860 ++++ tools/vmap/model.c | 943 ++++ tools/vmap/patch.c | 569 ++ tools/vmap/path_init.c | 643 +++ tools/vmap/portals.c | 1012 ++++ tools/vmap/prtfile.c | 394 ++ tools/vmap/shaders.c | 2152 ++++++++ tools/vmap/surface.c | 3822 ++++++++++++++ tools/vmap/surface_extra.c | 435 ++ tools/vmap/surface_foliage.c | 337 ++ tools/vmap/surface_fur.c | 130 + tools/vmap/surface_meta.c | 1969 +++++++ tools/vmap/tjunction.c | 720 +++ tools/vmap/tree.c | 155 + tools/vmap/vis.c | 1231 +++++ tools/vmap/visflow.c | 1746 +++++++ tools/vmap/vmap.h | 2555 +++++++++ tools/vmap/writebsp.c | 638 +++ 832 files changed, 233873 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE-GPL create mode 100644 LICENSE-LGPL create mode 100755 build_binarydist.sh create mode 100644 build_contents.txt create mode 100644 cmake/FindGLIB.cmake create mode 100644 cmake/FindGTK2.cmake create mode 100644 cmake/FindGTK3.cmake create mode 100644 cmake/FindGtkGLExt.cmake create mode 100644 cmake/FindMinizip.cmake create mode 100644 cmake/FindPango.cmake create mode 100644 cmake/scripts/package.cmake create mode 100755 compile_debug.sh create mode 100755 compile_release.sh create mode 100644 contrib/CMakeLists.txt create mode 100644 contrib/brushexport/CMakeLists.txt create mode 100644 contrib/brushexport/brushexport.def 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/prtview/AboutDialog.cpp create mode 100644 contrib/prtview/AboutDialog.h create mode 100644 contrib/prtview/CMakeLists.txt 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/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 icon.png create mode 100644 include/CMakeLists.txt create mode 100644 include/aboutmsg.default create mode 100644 include/aboutmsg.h create mode 100644 include/crypt.h create mode 100644 include/cullable.h create mode 100644 include/defaults.h create mode 100644 include/dpkdeps.h create mode 100644 include/editable.h create mode 100644 include/iarchive.h create mode 100644 include/ibrush.h create mode 100644 include/icamera.h create mode 100644 include/idatastream.h create mode 100644 include/ieclass.h create mode 100644 include/ientity.h create mode 100644 include/ifilesystem.h create mode 100644 include/ifiletypes.h create mode 100644 include/ifilter.h create mode 100644 include/igl.h create mode 100644 include/iglrender.h create mode 100644 include/igtkgl.h create mode 100644 include/iimage.h create mode 100644 include/imap.h create mode 100644 include/imodel.h create mode 100644 include/ioapi_buf.h create mode 100644 include/ioapi_mem.h create mode 100644 include/ipatch.h create mode 100644 include/iplugin.h create mode 100644 include/ireference.h create mode 100644 include/irender.h create mode 100644 include/iscenegraph.h create mode 100644 include/iscriplib.h create mode 100644 include/iselection.h create mode 100644 include/ishaders.h create mode 100644 include/itexdef.h create mode 100644 include/itextstream.h create mode 100644 include/itextures.h create mode 100644 include/itoolbar.h create mode 100644 include/iundo.h create mode 100644 include/mapfile.h create mode 100644 include/modelskin.h create mode 100644 include/moduleobserver.h create mode 100644 include/modulesystem.h create mode 100644 include/nameable.h create mode 100644 include/namespace.h create mode 100644 include/preferencesystem.cpp create mode 100644 include/preferencesystem.h create mode 100644 include/qerplugin.h create mode 100644 include/renderable.h create mode 100644 include/selectable.h create mode 100644 include/stream_version.h create mode 100644 include/unzip.h create mode 100644 include/version.h create mode 100644 include/warnings.h create mode 100644 include/windowobserver.h create mode 100644 include/zip.h create mode 100644 libs/CMakeLists.txt create mode 100644 libs/_.cpp create mode 100644 libs/archivelib.h create mode 100644 libs/bytebool.h create mode 100644 libs/bytestreamutils.h create mode 100644 libs/character.h create mode 100644 libs/cmdlib.h create mode 100644 libs/cmdlib/CMakeLists.txt create mode 100644 libs/cmdlib/cmdlib.cpp create mode 100644 libs/container/CMakeLists.txt create mode 100644 libs/container/array.cpp create mode 100644 libs/container/array.h create mode 100644 libs/container/cache.h create mode 100644 libs/container/container.h 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.h create mode 100644 libs/convert.h create mode 100644 libs/ddslib.h create mode 100644 libs/ddslib/CMakeLists.txt create mode 100644 libs/ddslib/ddslib.c create mode 100644 libs/debugging/CMakeLists.txt create mode 100644 libs/debugging/debugging.cpp create mode 100644 libs/debugging/debugging.h create mode 100644 libs/dragplanes.h create mode 100644 libs/eclasslib.h create mode 100644 libs/entitylib.h create mode 100644 libs/entityxml.h create mode 100644 libs/etclib.c create mode 100644 libs/etclib.h create mode 100644 libs/etclib/CMakeLists.txt create mode 100644 libs/filematch.c create mode 100644 libs/filematch.h create mode 100644 libs/filematch/CMakeLists.txt create mode 100644 libs/fs_filesystem.h create mode 100644 libs/fs_path.h create mode 100644 libs/generic/CMakeLists.txt create mode 100644 libs/generic/arrayrange.h 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/constant.cpp create mode 100644 libs/generic/constant.h create mode 100644 libs/generic/enumeration.h 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.h 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.h create mode 100644 libs/globaldefs.h create mode 100644 libs/gtkutil/CMakeLists.txt 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.h 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/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.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.h create mode 100644 libs/instancelib.h create mode 100644 libs/l_net/CMakeLists.txt 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_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/maplib.h create mode 100644 libs/math/CMakeLists.txt create mode 100644 libs/math/_.cpp create mode 100644 libs/math/aabb.h 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.h create mode 100644 libs/math/line.h create mode 100644 libs/math/matrix.h create mode 100644 libs/math/pi.h create mode 100644 libs/math/plane.h create mode 100644 libs/math/quaternion.h create mode 100644 libs/math/vector.h create mode 100644 libs/mathlib.h create mode 100644 libs/mathlib/CMakeLists.txt 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/ray.c create mode 100644 libs/md5lib.h create mode 100644 libs/md5lib/md5lib.c create mode 100644 libs/memory/CMakeLists.txt create mode 100644 libs/memory/allocator.cpp create mode 100644 libs/memory/allocator.h create mode 100644 libs/moduleobservers.h create mode 100644 libs/modulesystem/CMakeLists.txt create mode 100644 libs/modulesystem/moduleregistry.h 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/CMakeLists.txt create mode 100644 libs/os/_.cpp create mode 100644 libs/os/dir.h create mode 100644 libs/os/file.h create mode 100644 libs/os/path.h create mode 100644 libs/picomodel.h create mode 100644 libs/picomodel/CMakeLists.txt 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/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_iqm.c 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.h create mode 100644 libs/profile/CMakeLists.txt 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/property.h create mode 100644 libs/render.h create mode 100644 libs/scenelib.h create mode 100644 libs/script/CMakeLists.txt create mode 100644 libs/script/_.cpp create mode 100644 libs/script/scripttokeniser.h create mode 100644 libs/script/scripttokenwriter.h create mode 100644 libs/selectionlib.h create mode 100644 libs/shaderlib.h create mode 100644 libs/signal/CMakeLists.txt 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.h create mode 100644 libs/splines/CMakeLists.txt 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.h create mode 100644 libs/stream/CMakeLists.txt create mode 100644 libs/stream/_.cpp create mode 100644 libs/stream/filestream.h create mode 100644 libs/stream/memstream.h create mode 100644 libs/stream/stringstream.h create mode 100644 libs/stream/textfilestream.h create mode 100644 libs/stream/textstream.h create mode 100644 libs/string/CMakeLists.txt create mode 100644 libs/string/pooledstring.cpp create mode 100644 libs/string/pooledstring.h create mode 100644 libs/string/string.h create mode 100644 libs/string/stringfwd.h create mode 100644 libs/stringio.h create mode 100644 libs/texturelib.h create mode 100644 libs/transformlib.h create mode 100644 libs/traverselib.h create mode 100644 libs/typesystem.h create mode 100644 libs/uilib/CMakeLists.txt create mode 100644 libs/uilib/uilib.cpp create mode 100644 libs/uilib/uilib.h create mode 100644 libs/undolib.h create mode 100644 libs/uniquenames.h create mode 100644 libs/versionlib.h create mode 100644 libs/xml/CMakeLists.txt create mode 100644 libs/xml/ixml.h create mode 100644 libs/xml/xmlelement.h 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.h create mode 100644 plugins/CMakeLists.txt create mode 100644 plugins/archivezip/CMakeLists.txt 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/pkzip.h create mode 100644 plugins/archivezip/plugin.cpp create mode 100644 plugins/archivezip/zlibstream.h create mode 100644 plugins/config.mk create mode 100644 plugins/entity/CMakeLists.txt create mode 100644 plugins/entity/angle.h create mode 100644 plugins/entity/angles.h create mode 100644 plugins/entity/colour.h 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/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.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.h create mode 100644 plugins/entity/modelskinkey.h create mode 100644 plugins/entity/namedentity.h create mode 100644 plugins/entity/namekeys.h create mode 100644 plugins/entity/origin.h create mode 100644 plugins/entity/plugin.cpp create mode 100644 plugins/entity/prop_dynamic.cpp create mode 100644 plugins/entity/prop_dynamic.h create mode 100644 plugins/entity/rotation.h 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/CMakeLists.txt 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/imageq3.def create mode 100644 plugins/image/jpeg.cpp create mode 100644 plugins/image/jpeg.h create mode 100644 plugins/image/ktx.cpp create mode 100644 plugins/image/ktx.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/iqmmodel/CMakeLists.txt create mode 100644 plugins/iqmmodel/iqm.cpp create mode 100644 plugins/iqmmodel/iqm.h create mode 100644 plugins/iqmmodel/modeliqm.def create mode 100644 plugins/iqmmodel/plugin.cpp create mode 100644 plugins/iqmmodel/plugin.h create mode 100644 plugins/mapq3/CMakeLists.txt create mode 100644 plugins/mapq3/mapq3.def 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/write.cpp create mode 100644 plugins/mapq3/write.h create mode 100644 plugins/md3model/model.h create mode 100644 plugins/model/CMakeLists.txt 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/plugin.cpp create mode 100644 plugins/shaders/CMakeLists.txt create mode 100644 plugins/shaders/plugin.cpp create mode 100644 plugins/shaders/shaders.cpp create mode 100644 plugins/shaders/shaders.h create mode 100644 plugins/shaders/shadersq3.def create mode 100644 plugins/vfspk3/CMakeLists.txt 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/vfsq3.def create mode 100644 radiant/CMakeLists.txt 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/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/worldspawn.ico create mode 100644 radiant/worldspawn.rc 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 regression_tests/q3map2/base_winding/README.txt create mode 100644 regression_tests/q3map2/base_winding/base_winding_logging.patch create mode 100644 regression_tests/q3map2/base_winding/maps/base_winding.map create mode 100644 regression_tests/q3map2/coarse_snap_normal/README.txt create mode 100644 regression_tests/q3map2/coarse_snap_normal/maps/coarse_snap_normal.map create mode 100644 regression_tests/q3map2/decal_misalignment/README.txt create mode 100644 regression_tests/q3map2/decal_misalignment/maps/decal_misalignment.map create mode 100644 regression_tests/q3map2/decal_misalignment/scripts/radiant_regression_tests.shader create mode 100644 regression_tests/q3map2/degenerate_winding/README.txt create mode 100644 regression_tests/q3map2/degenerate_winding/maps/degenerate_winding.map create mode 100644 regression_tests/q3map2/degenerate_winding2/README.txt create mode 100644 regression_tests/q3map2/degenerate_winding2/maps/degenerate_winding2.map create mode 100644 regression_tests/q3map2/degenerate_winding3/README.txt create mode 100644 regression_tests/q3map2/degenerate_winding3/maps/degenerate_winding3.map create mode 100644 regression_tests/q3map2/disappearing_sliver/README.txt create mode 100644 regression_tests/q3map2/disappearing_sliver/maps/disappearing_sliver.map create mode 100644 regression_tests/q3map2/disappearing_sliver/winding_logging.patch create mode 100644 regression_tests/q3map2/disappearing_sliver2/README.txt create mode 100644 regression_tests/q3map2/disappearing_sliver2/maps/disappearing_sliver2.map create mode 100644 regression_tests/q3map2/disappearing_sliver3/NOTES.txt create mode 100644 regression_tests/q3map2/disappearing_sliver3/README.txt create mode 100644 regression_tests/q3map2/disappearing_sliver3/maps/disappearing_sliver3.map create mode 100644 regression_tests/q3map2/duplicate_plane/README.txt create mode 100644 regression_tests/q3map2/duplicate_plane/maps/duplicate_plane.map create mode 100644 regression_tests/q3map2/model_clipping_45_degrees/README.txt create mode 100644 regression_tests/q3map2/model_clipping_45_degrees/maps/model_clipping_45_degrees.map create mode 100644 regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_45.ase create mode 100644 regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_shallow.ase create mode 100644 regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_steep.ase create mode 100644 regression_tests/q3map2/model_clipping_45_degrees/scripts/radiant_regression_tests.shader create mode 100644 regression_tests/q3map2/patch_seam/README.txt create mode 100644 regression_tests/q3map2/patch_seam/maps/patch_seam.map create mode 100644 regression_tests/q3map2/patch_seam/textures/radiant_regression_tests/green.tga create mode 100644 regression_tests/q3map2/patch_seam/textures/radiant_regression_tests/tile.tga create mode 100644 regression_tests/q3map2/piercing_triangle/README.txt create mode 100644 regression_tests/q3map2/piercing_triangle/maps/piercing_triangle.map create mode 100644 regression_tests/q3map2/plane_aliasing/README.txt create mode 100644 regression_tests/q3map2/plane_aliasing/maps/plane_aliasing.map create mode 100644 regression_tests/q3map2/segmentation_fault/README.txt create mode 100644 regression_tests/q3map2/segmentation_fault/maps/segmentation_fault.map create mode 100644 regression_tests/q3map2/snap_plane/README.txt create mode 100644 regression_tests/q3map2/snap_plane/maps/snap_plane.map create mode 100644 regression_tests/q3map2/sparkly_seam/README.txt create mode 100644 regression_tests/q3map2/sparkly_seam/maps/sparkly_seam.map create mode 100644 resources/Resources/FileIcon_.map.tiff create mode 100644 resources/Resources/Info-gnustep.plist create mode 100644 resources/Resources/icon.tiff create mode 100755 resources/WorldSpawn create mode 100644 resources/bitmaps/black.xpm create mode 100644 resources/bitmaps/brush_flipx.xpm create mode 100644 resources/bitmaps/brush_flipy.xpm create mode 100644 resources/bitmaps/brush_flipz.xpm create mode 100644 resources/bitmaps/brush_rotatex.xpm create mode 100644 resources/bitmaps/brush_rotatey.xpm create mode 100644 resources/bitmaps/brush_rotatez.xpm create mode 100644 resources/bitmaps/cap_bevel.xpm create mode 100644 resources/bitmaps/cap_curve.xpm create mode 100644 resources/bitmaps/cap_cylinder.xpm create mode 100644 resources/bitmaps/cap_endcap.xpm create mode 100644 resources/bitmaps/cap_ibevel.xpm create mode 100644 resources/bitmaps/cap_iendcap.xpm create mode 100644 resources/bitmaps/console.xpm create mode 100644 resources/bitmaps/copy.xpm create mode 100644 resources/bitmaps/cut.xpm create mode 100644 resources/bitmaps/dontselectcurve.xpm create mode 100644 resources/bitmaps/dontselectmodel.xpm create mode 100644 resources/bitmaps/ellipsis.xpm create mode 100644 resources/bitmaps/entities.xpm create mode 100644 resources/bitmaps/file_new.xpm create mode 100644 resources/bitmaps/file_open.xpm create mode 100644 resources/bitmaps/file_save.xpm create mode 100644 resources/bitmaps/icon.xpm create mode 100644 resources/bitmaps/lightinspector.xpm create mode 100644 resources/bitmaps/logo.xpm create mode 100644 resources/bitmaps/modify_edges.xpm create mode 100644 resources/bitmaps/modify_faces.xpm create mode 100644 resources/bitmaps/modify_vertices.xpm create mode 100644 resources/bitmaps/noFalloff.xpm create mode 100644 resources/bitmaps/notex.tga create mode 100644 resources/bitmaps/paste.xpm create mode 100644 resources/bitmaps/patch_bend.xpm create mode 100644 resources/bitmaps/patch_drilldown.xpm create mode 100644 resources/bitmaps/patch_insdel.xpm create mode 100644 resources/bitmaps/patch_showboundingbox.xpm create mode 100644 resources/bitmaps/patch_weld.xpm create mode 100644 resources/bitmaps/patch_wireframe.xpm create mode 100644 resources/bitmaps/popup_selection.xpm create mode 100644 resources/bitmaps/redo.xpm create mode 100644 resources/bitmaps/refresh_models.xpm create mode 100644 resources/bitmaps/scalelockx.xpm create mode 100644 resources/bitmaps/scalelocky.xpm create mode 100644 resources/bitmaps/scalelockz.xpm create mode 100644 resources/bitmaps/select_mouseresize.xpm create mode 100644 resources/bitmaps/select_mouserotate.xpm create mode 100644 resources/bitmaps/select_mousescale.xpm create mode 100644 resources/bitmaps/select_mousetranslate.xpm create mode 100644 resources/bitmaps/selection_csgmerge.xpm create mode 100644 resources/bitmaps/selection_csgsubtract.xpm create mode 100644 resources/bitmaps/selection_makehollow.xpm create mode 100644 resources/bitmaps/selection_makeroom.xpm create mode 100644 resources/bitmaps/selection_selectcompletetall.xpm create mode 100644 resources/bitmaps/selection_selectinside.xpm create mode 100644 resources/bitmaps/selection_selectpartialtall.xpm create mode 100644 resources/bitmaps/selection_selecttouching.xpm create mode 100644 resources/bitmaps/shadernotex.tga create mode 100644 resources/bitmaps/show_entities.xpm create mode 100644 resources/bitmaps/side_clipper.xpm create mode 100644 resources/bitmaps/side_edges.xpm create mode 100644 resources/bitmaps/side_entities.xpm create mode 100644 resources/bitmaps/side_faces.xpm create mode 100644 resources/bitmaps/side_find.xpm create mode 100644 resources/bitmaps/side_patch.xpm create mode 100644 resources/bitmaps/side_resize.xpm create mode 100644 resources/bitmaps/side_rotate.xpm create mode 100644 resources/bitmaps/side_scale.xpm create mode 100644 resources/bitmaps/side_surface.xpm create mode 100644 resources/bitmaps/side_textures.xpm create mode 100644 resources/bitmaps/side_transform.xpm create mode 100644 resources/bitmaps/side_vertices.xpm create mode 100644 resources/bitmaps/splash.xcf create mode 100644 resources/bitmaps/splash.xpm create mode 100644 resources/bitmaps/texture_browser.xpm create mode 100644 resources/bitmaps/texture_lock.xpm create mode 100644 resources/bitmaps/textures_popup.xpm create mode 100644 resources/bitmaps/undo.xpm create mode 100644 resources/bitmaps/view_cameratoggle.xpm create mode 100644 resources/bitmaps/view_cameraupdate.xpm create mode 100644 resources/bitmaps/view_change.xpm create mode 100644 resources/bitmaps/view_clipper.xpm create mode 100644 resources/bitmaps/view_cubicclipping.xpm create mode 100644 resources/bitmaps/view_entity.xpm create mode 100644 resources/bitmaps/white.xpm create mode 100644 resources/bitmaps/window1.xpm create mode 100644 resources/bitmaps/window2.xpm create mode 100644 resources/bitmaps/window3.xpm create mode 100644 resources/bitmaps/window4.xpm create mode 100644 resources/games/platform.game create mode 100644 resources/gl/lighting_DBS_XY_Z_arbfp1.cg create mode 100644 resources/gl/lighting_DBS_XY_Z_arbvp1.cg create mode 100644 resources/gl/lighting_DBS_omni_fp.glp create mode 100644 resources/gl/lighting_DBS_omni_fp.glsl create mode 100644 resources/gl/lighting_DBS_omni_vp.glp create mode 100644 resources/gl/lighting_DBS_omni_vp.glsl create mode 100644 resources/gl/utils.cg create mode 100644 resources/gl/zfill_arbfp1.cg create mode 100644 resources/gl/zfill_arbvp1.cg create mode 100644 resources/gl/zfill_fp.glp create mode 100644 resources/gl/zfill_fp.glsl create mode 100644 resources/gl/zfill_vp.glp create mode 100644 resources/gl/zfill_vp.glsl create mode 100644 resources/global.xlink create mode 100644 resources/platform.game/default_build_menu.xml create mode 100644 resources/platform.game/platform/entities.def create mode 100644 tools/CMakeLists.txt create mode 100644 tools/common/aselib.c create mode 100644 tools/common/aselib.h create mode 100644 tools/common/bspfile.c create mode 100644 tools/common/bspfile.h create mode 100644 tools/common/cmdlib.c create mode 100644 tools/common/cmdlib.h create mode 100644 tools/common/imagelib.c create mode 100644 tools/common/imagelib.h create mode 100644 tools/common/inout.c create mode 100644 tools/common/inout.h create mode 100644 tools/common/jpeg.c create mode 100644 tools/common/l3dslib.c create mode 100644 tools/common/l3dslib.h create mode 100644 tools/common/md4.c create mode 100644 tools/common/md4.h create mode 100644 tools/common/mutex.c create mode 100644 tools/common/mutex.h create mode 100644 tools/common/polylib.c create mode 100644 tools/common/polylib.h create mode 100644 tools/common/polyset.h create mode 100644 tools/common/qfiles.h create mode 100644 tools/common/qthreads.h create mode 100644 tools/common/scriplib.c create mode 100644 tools/common/scriplib.h create mode 100644 tools/common/surfaceflags.h create mode 100644 tools/common/threads.c create mode 100644 tools/common/trilib.c create mode 100644 tools/common/trilib.h create mode 100644 tools/common/vfs.c create mode 100644 tools/common/vfs.h create mode 100644 tools/vmap/brush.c create mode 100644 tools/vmap/brush_primit.c create mode 100644 tools/vmap/bsp.c create mode 100644 tools/vmap/bsp_analyze.c create mode 100644 tools/vmap/bsp_info.c create mode 100644 tools/vmap/bsp_scale.c create mode 100644 tools/vmap/bspfile_abstract.c create mode 100644 tools/vmap/bspfile_ibsp.c create mode 100644 tools/vmap/bspfile_rbsp.c create mode 100644 tools/vmap/convert_ase.c create mode 100644 tools/vmap/convert_bsp.c create mode 100644 tools/vmap/convert_map.c create mode 100644 tools/vmap/convert_obj.c create mode 100644 tools/vmap/decals.c create mode 100644 tools/vmap/exportents.c create mode 100644 tools/vmap/facebsp.c create mode 100644 tools/vmap/fixaas.c create mode 100644 tools/vmap/fog.c create mode 100644 tools/vmap/game__null.h create mode 100644 tools/vmap/game_fte.h create mode 100644 tools/vmap/help.c create mode 100644 tools/vmap/image.c create mode 100644 tools/vmap/leakfile.c create mode 100644 tools/vmap/light.c create mode 100644 tools/vmap/light_bounce.c create mode 100644 tools/vmap/light_shadows.c create mode 100644 tools/vmap/light_trace.c create mode 100644 tools/vmap/light_ydnar.c create mode 100644 tools/vmap/lightmaps.c create mode 100644 tools/vmap/lightmaps_ydnar.c create mode 100644 tools/vmap/main.c create mode 100644 tools/vmap/map.c create mode 100644 tools/vmap/mesh.c create mode 100644 tools/vmap/model.c create mode 100644 tools/vmap/patch.c create mode 100644 tools/vmap/path_init.c create mode 100644 tools/vmap/portals.c create mode 100644 tools/vmap/prtfile.c create mode 100644 tools/vmap/shaders.c create mode 100644 tools/vmap/surface.c create mode 100644 tools/vmap/surface_extra.c create mode 100644 tools/vmap/surface_foliage.c create mode 100644 tools/vmap/surface_fur.c create mode 100644 tools/vmap/surface_meta.c create mode 100644 tools/vmap/tjunction.c create mode 100644 tools/vmap/tree.c create mode 100644 tools/vmap/vis.c create mode 100644 tools/vmap/visflow.c create mode 100644 tools/vmap/vmap.h create mode 100644 tools/vmap/writebsp.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d7d5c06 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,275 @@ +cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +project(WorldSpawn C CXX) + +option(BUILD_RADIANT "Build the GUI" ON) + +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/install" CACHE PATH "..." FORCE) +endif () + +#----------------------------------------------------------------------- +# Version +#----------------------------------------------------------------------- + +# CMake 3.0+ would allow this in project() +set(WorldSpawn_VERSION_MAJOR 1) +set(WorldSpawn_VERSION_MINOR 0) +set(WorldSpawn_VERSION_PATCH 0) +set(WorldSpawn_VERSION "${WorldSpawn_VERSION_MAJOR}.${WorldSpawn_VERSION_MINOR}.${WorldSpawn_VERSION_PATCH}") + +SET(CMAKE_C_COMPILER gcc-9) +SET(CMAKE_CXX_COMPILER g++-9) + +file(WRITE "${PROJECT_BINARY_DIR}/WorldSpawn_MAJOR" ${WorldSpawn_VERSION_MAJOR}) +file(WRITE "${PROJECT_BINARY_DIR}/WorldSpawn_MINOR" ${WorldSpawn_VERSION_MINOR}) +file(WRITE "${PROJECT_BINARY_DIR}/WorldSpawn_PATCH" ${WorldSpawn_VERSION_PATCH}) + +#set(WorldSpawn_ABOUTMSG "Custom build" CACHE STRING "About message") + +find_package(Git REQUIRED) +execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) +set(WorldSpawn_VERSION_STRING "${WorldSpawn_VERSION}n") +if (GIT_VERSION) + set(WorldSpawn_VERSION_STRING "${WorldSpawn_VERSION_STRING}-git-${GIT_VERSION}") +endif () + +message(STATUS "Building ${PROJECT_NAME} ${WorldSpawn_VERSION_STRING} ${WorldSpawn_ABOUTMSG}") + +#----------------------------------------------------------------------- +# Language standard +#----------------------------------------------------------------------- + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +if (CMAKE_VERSION VERSION_LESS "3.1") + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR CMAKE_COMPILER_IS_GNUCXX) + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(--std=c++${CMAKE_CXX_STANDARD} STD_CXX) + if (STD_CXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++${CMAKE_CXX_STANDARD}") + else () + message(SEND_ERROR "Requires C++${CMAKE_CXX_STANDARD} or better") + endif () + else () + message(WARNING "Unrecognized compiler: ${CMAKE_CXX_COMPILER_ID}, make sure it supports C++${CMAKE_CXX_STANDARD}") + endif () +endif () + +#----------------------------------------------------------------------- +# Flags +#----------------------------------------------------------------------- + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") +macro(addflags_c args) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${args}") +endmacro() +macro(addflags_cxx args) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${args}") +endmacro() +macro(addflags args) + addflags_c("${args}") + addflags_cxx("${args}") +endmacro() +addflags("-fno-strict-aliasing") +if (NOT WIN32) + addflags("-fvisibility=hidden") +endif () + +if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + #disabled due to GTK bug addflags("-Werror") + addflags("-pedantic-errors") +endif () + +addflags("-Wall") +addflags("-Wextra") +addflags("-pedantic") + +addflags_c("-Wno-deprecated-declarations") # vfs.c: g_strdown + +addflags("-Wno-unused-function") +addflags("-Wno-unused-variable") +addflags("-Wno-unused-parameter") + +set(CMAKE_POSITION_INDEPENDENT_CODE 1) +set(GTK_TARGET 2 CACHE STRING "GTK target") +add_definitions(-DGTK_TARGET=${GTK_TARGET}) + +#----------------------------------------------------------------------- +# Defs +#----------------------------------------------------------------------- + +add_definitions(-DWorldSpawn_VERSION="${WorldSpawn_VERSION}") +add_definitions(-DWorldSpawn_MAJOR_VERSION="${WorldSpawn_VERSION_MAJOR}") +add_definitions(-DWorldSpawn_MINOR_VERSION="${WorldSpawn_VERSION_MINOR}") +add_definitions(-DWorldSpawn_PATCH_VERSION="${WorldSpawn_VERSION_PATCH}") + +add_definitions(-DWorldSpawn_ABOUTMSG="${WorldSpawn_ABOUT}") + +if (NOT CMAKE_BUILD_TYPE MATCHES Release) + add_definitions(-D_DEBUG=1) +endif () + +macro(disable_deprecated name gtk2only) + add_definitions(-D${name}_DISABLE_SINGLE_INCLUDES) + if ((${gtk2only} EQUAL 0) OR (GTK_TARGET EQUAL 2)) + add_definitions(-D${name}_DISABLE_DEPRECATED) + endif () +endmacro() + +disable_deprecated(ATK 0) +disable_deprecated(G 0) +disable_deprecated(GDK 0) +disable_deprecated(GDK_PIXBUF 0) +disable_deprecated(GTK 1) +disable_deprecated(PANGO 0) + +if (APPLE) + option(XWINDOWS "Build against X11" ON) + add_definitions( + -DPOSIX=1 + ) +elseif (WIN32) + add_definitions( + -DWIN32=1 + -D_WIN32=1 + ) +else () + set(XWINDOWS ON) + add_definitions( + -DPOSIX=1 + ) +endif () + +if (XWINDOWS) + find_package(X11 REQUIRED) + include_directories(${X11_INCLUDE_DIR}) + add_definitions(-DXWINDOWS=1) +endif () + +include_directories("${PROJECT_SOURCE_DIR}/include") +include_directories("${PROJECT_SOURCE_DIR}/libs") + +if (WIN32 AND NOT CMAKE_CROSSCOMPILING) + set(BUNDLE_LIBRARIES_DEFAULT ON) +else () + set(BUNDLE_LIBRARIES_DEFAULT OFF) +endif () +option(BUNDLE_LIBRARIES "Bundle libraries" ${BUNDLE_LIBRARIES_DEFAULT}) + +macro(copy_dlls target) + if (BUNDLE_LIBRARIES) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND bash + ARGS -c "ldd '$' | grep -v /c/Windows | awk '{ print $1 }' | while read dll; do cp \"$(which $dll)\" '${PROJECT_BINARY_DIR}'; done" + VERBATIM + ) + endif () +endmacro() + +#----------------------------------------------------------------------- +# Libraries +#----------------------------------------------------------------------- + +add_subdirectory(libs) +add_subdirectory(include) + +#----------------------------------------------------------------------- +# Plugins +#----------------------------------------------------------------------- + +if (BUILD_RADIANT) + add_subdirectory(contrib) +endif () + +#----------------------------------------------------------------------- +# Modules +#----------------------------------------------------------------------- + +if (BUILD_RADIANT) + add_subdirectory(plugins) +endif () + +#----------------------------------------------------------------------- +# Radiant +#----------------------------------------------------------------------- + +if (CMAKE_EXECUTABLE_SUFFIX) + string(REGEX REPLACE "^[.]" "" WorldSpawn_EXECUTABLE ${CMAKE_EXECUTABLE_SUFFIX}) +else () + execute_process( + COMMAND uname -m + OUTPUT_VARIABLE WorldSpawn_EXECUTABLE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif () + +macro(radiant_tool name) + add_executable(${name} ${ARGN}) + install( + TARGETS ${name} + RUNTIME DESTINATION . + ) + if (NOT (CMAKE_EXECUTABLE_SUFFIX STREQUAL ".${WorldSpawn_EXECUTABLE}")) + add_custom_command(TARGET ${name} POST_BUILD + COMMAND ln -f -s "$" "${PROJECT_BINARY_DIR}/${name}.${WorldSpawn_EXECUTABLE}" + VERBATIM + ) + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + ${name}${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_INSTALL_PREFIX}/${name}.${WorldSpawn_EXECUTABLE}) + ") + endif () +endmacro() + +if (BUILD_RADIANT) + add_subdirectory(radiant _radiant) + set_target_properties(worldspawn PROPERTIES + COMPILE_DEFINITIONS WorldSpawn_EXECUTABLE="${WorldSpawn_EXECUTABLE}" + ) +endif () + +#----------------------------------------------------------------------- +# Tools +#----------------------------------------------------------------------- + +add_subdirectory(tools) + +file(GLOB DATA_FILES "${PROJECT_SOURCE_DIR}/resources/*") + +if (NOT (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)) + # Copy data files from sources to the build directory + message(STATUS "Copying data files") + file(COPY ${DATA_FILES} DESTINATION "${PROJECT_BINARY_DIR}") +endif () + +#----------------------------------------------------------------------- +# Install +#----------------------------------------------------------------------- + +install( + FILES + "${PROJECT_BINARY_DIR}/WorldSpawn_MAJOR" + "${PROJECT_BINARY_DIR}/WorldSpawn_MINOR" + "${PROJECT_BINARY_DIR}/WorldSpawn_PATCH" + DESTINATION . +) + +install( + DIRECTORY + resources/ + DESTINATION . +) + +install( + DIRECTORY + DESTINATION . + OPTIONAL +) + +include(cmake/scripts/package.cmake) diff --git a/LICENSE-GPL b/LICENSE-GPL new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/LICENSE-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/LICENSE-LGPL b/LICENSE-LGPL new file mode 100644 index 0000000..962a9fb --- /dev/null +++ b/LICENSE-LGPL @@ -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/build_binarydist.sh b/build_binarydist.sh new file mode 100755 index 0000000..f379c63 --- /dev/null +++ b/build_binarydist.sh @@ -0,0 +1,2 @@ +#!/bin/sh +zip worldspawn_bin.zip -@ < build_contents.txt diff --git a/build_contents.txt b/build_contents.txt new file mode 100644 index 0000000..4596d0d --- /dev/null +++ b/build_contents.txt @@ -0,0 +1,150 @@ +WorldSpawn.app +WorldSpawn.app/base +WorldSpawn.app/base/textures +WorldSpawn.app/base/textures/radiant +WorldSpawn.app/base/textures/radiant/notex.png +WorldSpawn.app/base/textures/radiant/shadernotex.png +WorldSpawn.app/bitmaps +WorldSpawn.app/bitmaps/black.png +WorldSpawn.app/bitmaps/brush_flipx.png +WorldSpawn.app/bitmaps/brush_flipy.png +WorldSpawn.app/bitmaps/brush_flipz.png +WorldSpawn.app/bitmaps/brush_rotatex.png +WorldSpawn.app/bitmaps/brush_rotatey.png +WorldSpawn.app/bitmaps/brush_rotatez.png +WorldSpawn.app/bitmaps/cap_bevel.png +WorldSpawn.app/bitmaps/cap_curve.png +WorldSpawn.app/bitmaps/cap_cylinder.png +WorldSpawn.app/bitmaps/cap_endcap.png +WorldSpawn.app/bitmaps/cap_ibevel.png +WorldSpawn.app/bitmaps/cap_iendcap.png +WorldSpawn.app/bitmaps/console.png +WorldSpawn.app/bitmaps/dontselectcurve.png +WorldSpawn.app/bitmaps/dontselectmodel.png +WorldSpawn.app/bitmaps/ellipsis.png +WorldSpawn.app/bitmaps/entities.png +WorldSpawn.app/bitmaps/file_open.png +WorldSpawn.app/bitmaps/file_save.png +WorldSpawn.app/bitmaps/icon.png +WorldSpawn.app/bitmaps/lightinspector.png +WorldSpawn.app/bitmaps/logo.png +WorldSpawn.app/bitmaps/modify_edges.png +WorldSpawn.app/bitmaps/modify_faces.png +WorldSpawn.app/bitmaps/modify_vertices.png +WorldSpawn.app/bitmaps/noFalloff.png +WorldSpawn.app/bitmaps/notex.png +WorldSpawn.app/bitmaps/patch_bend.png +WorldSpawn.app/bitmaps/patch_drilldown.png +WorldSpawn.app/bitmaps/patch_insdel.png +WorldSpawn.app/bitmaps/patch_showboundingbox.png +WorldSpawn.app/bitmaps/patch_weld.png +WorldSpawn.app/bitmaps/patch_wireframe.png +WorldSpawn.app/bitmaps/popup_selection.png +WorldSpawn.app/bitmaps/redo.png +WorldSpawn.app/bitmaps/refresh_models.png +WorldSpawn.app/bitmaps/scalelockx.png +WorldSpawn.app/bitmaps/scalelocky.png +WorldSpawn.app/bitmaps/scalelockz.png +WorldSpawn.app/bitmaps/selection_csgmerge.png +WorldSpawn.app/bitmaps/selection_csgsubtract.png +WorldSpawn.app/bitmaps/selection_makehollow.png +WorldSpawn.app/bitmaps/selection_makeroom.png +WorldSpawn.app/bitmaps/selection_selectcompletetall.png +WorldSpawn.app/bitmaps/selection_selectinside.png +WorldSpawn.app/bitmaps/selection_selectpartialtall.png +WorldSpawn.app/bitmaps/selection_selecttouching.png +WorldSpawn.app/bitmaps/select_mouseresize.png +WorldSpawn.app/bitmaps/select_mouserotate.png +WorldSpawn.app/bitmaps/select_mousescale.png +WorldSpawn.app/bitmaps/select_mousetranslate.png +WorldSpawn.app/bitmaps/shadernotex.png +WorldSpawn.app/bitmaps/show_entities.png +WorldSpawn.app/bitmaps/splash.png +WorldSpawn.app/bitmaps/texture_browser.png +WorldSpawn.app/bitmaps/texture_lock.png +WorldSpawn.app/bitmaps/textures_popup.png +WorldSpawn.app/bitmaps/undo.png +WorldSpawn.app/bitmaps/view_cameratoggle.png +WorldSpawn.app/bitmaps/view_cameraupdate.png +WorldSpawn.app/bitmaps/view_change.png +WorldSpawn.app/bitmaps/view_clipper.png +WorldSpawn.app/bitmaps/view_cubicclipping.png +WorldSpawn.app/bitmaps/view_entity.png +WorldSpawn.app/bitmaps/white.png +WorldSpawn.app/bitmaps/window1.png +WorldSpawn.app/bitmaps/window2.png +WorldSpawn.app/bitmaps/window3.png +WorldSpawn.app/bitmaps/window4.png +WorldSpawn.app/games +WorldSpawn.app/games/tw.game +WorldSpawn.app/gl +WorldSpawn.app/gl/lighting_DBS_omni_fp.glp +WorldSpawn.app/gl/lighting_DBS_omni_fp.glsl +WorldSpawn.app/gl/lighting_DBS_omni_vp.glp +WorldSpawn.app/gl/lighting_DBS_omni_vp.glsl +WorldSpawn.app/gl/lighting_DBS_XY_Z_arbfp1.cg +WorldSpawn.app/gl/lighting_DBS_XY_Z_arbvp1.cg +WorldSpawn.app/gl/utils.cg +WorldSpawn.app/gl/zfill_arbfp1.cg +WorldSpawn.app/gl/zfill_arbvp1.cg +WorldSpawn.app/gl/zfill_fp.glp +WorldSpawn.app/gl/zfill_fp.glsl +WorldSpawn.app/gl/zfill_vp.glp +WorldSpawn.app/gl/zfill_vp.glsl +WorldSpawn.app/global.xlink +WorldSpawn.app/modules +WorldSpawn.app/modules/libarchivezip.so +WorldSpawn.app/modules/libentity.so +WorldSpawn.app/modules/libimage.so +WorldSpawn.app/modules/libiqmmodel.so +WorldSpawn.app/modules/libmapq3.so +WorldSpawn.app/modules/libmodel.so +WorldSpawn.app/modules/libshaders.so +WorldSpawn.app/modules/libvfspk3.so +WorldSpawn.app/plugins +WorldSpawn.app/plugins/bitmaps +WorldSpawn.app/plugins/bitmaps/bobtoolz_caulk.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_cleanup.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_dropent.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_merge.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_poly.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_splitcol.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_split.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_splitrow.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_trainpathplot.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_treeplanter.png +WorldSpawn.app/plugins/bitmaps/bobtoolz_turnedge.png +WorldSpawn.app/plugins/bitmaps/ufoai_actorclip.png +WorldSpawn.app/plugins/bitmaps/ufoai_level1.png +WorldSpawn.app/plugins/bitmaps/ufoai_level2.png +WorldSpawn.app/plugins/bitmaps/ufoai_level3.png +WorldSpawn.app/plugins/bitmaps/ufoai_level4.png +WorldSpawn.app/plugins/bitmaps/ufoai_level5.png +WorldSpawn.app/plugins/bitmaps/ufoai_level6.png +WorldSpawn.app/plugins/bitmaps/ufoai_level7.png +WorldSpawn.app/plugins/bitmaps/ufoai_level8.png +WorldSpawn.app/plugins/bitmaps/ufoai_nodraw.png +WorldSpawn.app/plugins/bitmaps/ufoai_stepon.png +WorldSpawn.app/plugins/bitmaps/ufoai_weaponclip.png +WorldSpawn.app/plugins/bt +WorldSpawn.app/plugins/bt/bt-el1.txt +WorldSpawn.app/plugins/bt/bt-el2.txt +WorldSpawn.app/plugins/bt/door-tex-trim.txt +WorldSpawn.app/plugins/bt/door-tex.txt +WorldSpawn.app/plugins/bt/tp_ent.txt +WorldSpawn.app/plugins/libbobtoolz.so +WorldSpawn.app/plugins/libbrushexport.so +WorldSpawn.app/plugins/libprtview.so +WorldSpawn.app/plugins/libshaderplug.so +WorldSpawn.app/plugins/libsunplug.so +WorldSpawn.app/tw.game +WorldSpawn.app/tw.game/default_build_menu.xml +WorldSpawn.app/tw.game/game.xlink +WorldSpawn.app/tw.game/wastes +WorldSpawn.app/tw.game/wastes/entities.def +WorldSpawn.app/Resources +WorldSpawn.app/Resources/Info-gnustep.plist +WorldSpawn.app/Resources/icon.tiff +WorldSpawn.app/vmap +WorldSpawn.app/worldspawn +WorldSpawn.app/WorldSpawn diff --git a/cmake/FindGLIB.cmake b/cmake/FindGLIB.cmake new file mode 100644 index 0000000..4b1b265 --- /dev/null +++ b/cmake/FindGLIB.cmake @@ -0,0 +1,21 @@ +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + if (GLIB_FIND_REQUIRED) + set(_pkgconfig_REQUIRED REQUIRED) + endif () + pkg_check_modules(GLIB ${_pkgconfig_REQUIRED} glib-2.0) +else () + find_path(GLIB_INCLUDE_DIRS glib.h) + find_library(GLIB_LIBRARIES glib-2.0) + if (GLIB_INCLUDE_DIRS AND GLIB_LIBRARIES) + set(GLIB_FOUND 1) + if (NOT GLIB_FIND_QUIETLY) + message(STATUS "Found GLIB: ${GLIB_LIBRARIES}") + endif () + elseif (GLIB_FIND_REQUIRED) + message(SEND_ERROR "Could not find GLIB") + elseif (NOT GLIB_FIND_QUIETLY) + message(STATUS "Could not find GLIB") + endif () +endif () +mark_as_advanced(GLIB_INCLUDE_DIRS GLIB_LIBRARIES) diff --git a/cmake/FindGTK2.cmake b/cmake/FindGTK2.cmake new file mode 100644 index 0000000..ff20526 --- /dev/null +++ b/cmake/FindGTK2.cmake @@ -0,0 +1,21 @@ +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + if (GTK2_FIND_REQUIRED) + set(_pkgconfig_REQUIRED REQUIRED) + endif () + pkg_check_modules(GTK2 ${_pkgconfig_REQUIRED} gtk+-2.0) +else () + find_path(GTK2_INCLUDE_DIRS gtk.h) + # find_library(GTK2_LIBRARIES) + if (GTK2_INCLUDE_DIRS AND GTK2_LIBRARIES) + set(GTK2_FOUND 1) + if (NOT GTK2_FIND_QUIETLY) + message(STATUS "Found GTK2: ${GTK2_LIBRARIES}") + endif () + elseif (GTK2_FIND_REQUIRED) + message(SEND_ERROR "Could not find GTK2") + elseif (NOT GTK2_FIND_QUIETLY) + message(STATUS "Could not find GTK2") + endif () +endif () +mark_as_advanced(GTK2_INCLUDE_DIRS GTK2_LIBRARIES) diff --git a/cmake/FindGTK3.cmake b/cmake/FindGTK3.cmake new file mode 100644 index 0000000..7bef3b0 --- /dev/null +++ b/cmake/FindGTK3.cmake @@ -0,0 +1,21 @@ +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + if (GTK3_FIND_REQUIRED) + set(_pkgconfig_REQUIRED REQUIRED) + endif () + pkg_check_modules(GTK3 ${_pkgconfig_REQUIRED} gtk+-3.0) +else () + find_path(GTK3_INCLUDE_DIRS gtk.h) + # find_library(GTK3_LIBRARIES) + if (GTK3_INCLUDE_DIRS AND GTK3_LIBRARIES) + set(GTK3_FOUND 1) + if (NOT GTK3_FIND_QUIETLY) + message(STATUS "Found GTK3: ${GTK3_LIBRARIES}") + endif () + elseif (GTK3_FIND_REQUIRED) + message(SEND_ERROR "Could not find GTK3") + elseif (NOT GTK3_FIND_QUIETLY) + message(STATUS "Could not find GTK3") + endif () +endif () +mark_as_advanced(GTK3_INCLUDE_DIRS GTK3_LIBRARIES) diff --git a/cmake/FindGtkGLExt.cmake b/cmake/FindGtkGLExt.cmake new file mode 100644 index 0000000..b30a1e1 --- /dev/null +++ b/cmake/FindGtkGLExt.cmake @@ -0,0 +1,27 @@ +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + if (GtkGLExt_FIND_REQUIRED) + set(_pkgconfig_REQUIRED REQUIRED) + endif () + if (XWINDOWS) + pkg_check_modules(GtkGLExt ${_pkgconfig_REQUIRED} gtkglext-x11-1.0) + elseif (WIN32) + pkg_check_modules(GtkGLExt ${_pkgconfig_REQUIRED} gtkglext-win32-1.0) + else () + pkg_check_modules(GtkGLExt ${_pkgconfig_REQUIRED} gtkglext-quartz-1.0) + endif () +else () + find_path(GtkGLExt_INCLUDE_DIRS gtkglwidget.h) + # find_library(GtkGLExt_LIBRARIES) + if (GtkGLExt_INCLUDE_DIRS AND GtkGLExt_LIBRARIES) + set(GtkGLExt_FOUND 1) + if (NOT GtkGLExt_FIND_QUIETLY) + message(STATUS "Found GtkGLExt: ${GtkGLExt_LIBRARIES}") + endif () + elseif (GtkGLExt_FIND_REQUIRED) + message(SEND_ERROR "Could not find GtkGLExt") + elseif (NOT GtkGLExt_FIND_QUIETLY) + message(STATUS "Could not find GtkGLExt") + endif () +endif () +mark_as_advanced(GtkGLExt_INCLUDE_DIRS GtkGLExt_LIBRARIES) diff --git a/cmake/FindMinizip.cmake b/cmake/FindMinizip.cmake new file mode 100644 index 0000000..0de098f --- /dev/null +++ b/cmake/FindMinizip.cmake @@ -0,0 +1,21 @@ +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + if (Minizip_FIND_REQUIRED) + set(_pkgconfig_REQUIRED REQUIRED) + endif () + pkg_check_modules(Minizip ${_pkgconfig_REQUIRED} minizip) +else () + find_path(Minizip_INCLUDE_DIRS unzip.h) + # find_library(Minizip_LIBRARIES) + if (Minizip_INCLUDE_DIRS AND Minizip_LIBRARIES) + set(Minizip_FOUND 1) + if (NOT Minizip_FIND_QUIETLY) + message(STATUS "Found Minizip: ${Minizip_LIBRARIES}") + endif () + elseif (Minizip_FIND_REQUIRED) + message(SEND_ERROR "Could not find Minizip") + elseif (NOT Minizip_FIND_QUIETLY) + message(STATUS "Could not find Minizip") + endif () +endif () +mark_as_advanced(Minizip_INCLUDE_DIRS Minizip_LIBRARIES) diff --git a/cmake/FindPango.cmake b/cmake/FindPango.cmake new file mode 100644 index 0000000..67359ef --- /dev/null +++ b/cmake/FindPango.cmake @@ -0,0 +1,23 @@ +find_package(PkgConfig) +if (PKG_CONFIG_FOUND) + if (Pango_FIND_REQUIRED) + set(_pkgconfig_REQUIRED REQUIRED) + endif () + pkg_search_module(Pango ${_pkgconfig_REQUIRED} pango pangocairo) + pkg_search_module(PangoFT2 ${_pkgconfig_REQUIRED} pangoft2) +else () + # find_path(Pango_INCLUDE_DIRS) + # find_library(Pango_LIBRARIES) + if (Pango_INCLUDE_DIRS AND Pango_LIBRARIES) + set(Pango_FOUND 1) + if (NOT Pango_FIND_QUIETLY) + message(STATUS "Found Pango: ${Pango_LIBRARIES}") + endif () + elseif (Pango_FIND_REQUIRED) + message(SEND_ERROR "Could not find Pango") + elseif (NOT Pango_FIND_QUIETLY) + message(STATUS "Could not find Pango") + endif () +endif () +mark_as_advanced(Pango_INCLUDE_DIRS Pango_LIBRARIES) +mark_as_advanced(PangoFT2_INCLUDE_DIRS PangoFT2_LIBRARIES) diff --git a/cmake/scripts/package.cmake b/cmake/scripts/package.cmake new file mode 100644 index 0000000..8191297 --- /dev/null +++ b/cmake/scripts/package.cmake @@ -0,0 +1,16 @@ +set(CPACK_PACKAGE_NAME "WorldSpawn") +set(CPACK_PACKAGE_VERSION_MAJOR "${WorldSpawn_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${WorldSpawn_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${WorldSpawn_VERSION_PATCH}") + +# binary: --target package +set(CPACK_GENERATOR "ZIP") +set(CPACK_STRIP_FILES 1) + +# source: --target package_source +set(CPACK_SOURCE_GENERATOR "ZIP") +set(CPACK_SOURCE_IGNORE_FILES "/\\\\.git/;/build/;/install/") + +# configure +include(InstallRequiredSystemLibraries) +include(CPack) diff --git a/compile_debug.sh b/compile_debug.sh new file mode 100755 index 0000000..3de0d9a --- /dev/null +++ b/compile_debug.sh @@ -0,0 +1 @@ +cmake -G "Unix Makefiles" -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug && cmake --build build -- -j$(nproc) diff --git a/compile_release.sh b/compile_release.sh new file mode 100755 index 0000000..1b559f5 --- /dev/null +++ b/compile_release.sh @@ -0,0 +1 @@ +cmake -G "Unix Makefiles" -H. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build -- -j$(nproc) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt new file mode 100644 index 0000000..28e3816 --- /dev/null +++ b/contrib/CMakeLists.txt @@ -0,0 +1,16 @@ +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/plugins") + +add_custom_target(plugins) +macro(radiant_plugin name) + message(STATUS "Found Plugin ${name}") + add_library(${name} MODULE ${ARGN}) + add_dependencies(plugins ${name}) + copy_dlls(${name}) + install( + TARGETS ${name} + LIBRARY DESTINATION plugins + ) +endmacro() + +add_subdirectory(brushexport) +add_subdirectory(prtview) diff --git a/contrib/brushexport/CMakeLists.txt b/contrib/brushexport/CMakeLists.txt new file mode 100644 index 0000000..578588d --- /dev/null +++ b/contrib/brushexport/CMakeLists.txt @@ -0,0 +1,10 @@ +radiant_plugin(brushexport + callbacks.cpp callbacks.h + export.cpp export.h + interface.cpp + plugin.cpp plugin.h + support.cpp support.h + ) + +target_include_directories(brushexport PRIVATE uilib) +target_link_libraries(brushexport PRIVATE uilib) diff --git a/contrib/brushexport/brushexport.def b/contrib/brushexport/brushexport.def new file mode 100644 index 0000000..2174016 --- /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/callbacks.cpp b/contrib/brushexport/callbacks.cpp new file mode 100644 index 0000000..87b920a --- /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(ui::Widget w, gpointer data) + { + DestroyWindow(); + } + + void OnExportClicked(ui::Button button, gpointer user_data) + { + auto window = ui::Window::from(lookup_widget(button, "w_plugplug2")); + ASSERT_TRUE(window); + const char *cpath = GlobalRadiant().m_pfnFileDialog(window, false, "Save as Obj", 0, 0, false, false, true); + if (!cpath) { + return; + } + + std::string path(cpath); + + // get ignore list from ui + std::set ignore; + + auto view = ui::TreeView::from(lookup_widget(button, "t_materialist")); + ui::ListStore list = ui::ListStore::from(gtk_tree_view_get_model(view)); + + GtkTreeIter iter; + gboolean valid = gtk_tree_model_get_iter_first(list, &iter); + while (valid) { + gchar *data; + gtk_tree_model_get(list, &iter, 0, &data, -1); + globalOutputStream() << data << "\n"; + ignore.insert(std::string(data)); + g_free(data); + valid = gtk_tree_model_iter_next(list, &iter); + } + + for (std::set::iterator it(ignore.begin()); it != ignore.end(); ++it) { + globalOutputStream() << it->c_str() << "\n"; + } + + // collapse mode + collapsemode mode = COLLAPSE_NONE; + + auto radio = lookup_widget(button, "r_collapse"); + ASSERT_TRUE(radio); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio))) { + mode = COLLAPSE_ALL; + } else { + radio = lookup_widget(button, "r_collapsebymaterial"); + ASSERT_TRUE(radio); + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio))) { + mode = COLLAPSE_BY_MATERIAL; + } else { + radio = lookup_widget(button, "r_nocollapse"); + ASSERT_TRUE(radio); + ASSERT_TRUE(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio))); + mode = COLLAPSE_NONE; + } + } + + // export materials? + auto toggle = lookup_widget(button, "t_exportmaterials"); + ASSERT_TRUE(toggle); + + bool exportmat = FALSE; + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle))) { + exportmat = TRUE; + } + + // limit material names? + toggle = lookup_widget(button, "t_limitmatnames"); + ASSERT_TRUE(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(button, "t_objects"); + ASSERT_TRUE(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(ui::Button button, gpointer user_data) + { + auto edit = ui::Entry::from(lookup_widget(button, "ed_materialname")); + ASSERT_TRUE(edit); + + const gchar *name = gtk_entry_get_text(edit); + if (g_utf8_strlen(name, -1) > 0) { + ui::ListStore list = ui::ListStore::from( + gtk_tree_view_get_model(ui::TreeView::from(lookup_widget(button, "t_materialist")))); + list.append(0, name); + gtk_entry_set_text(edit, ""); + } + } + + void OnRemoveMaterial(ui::Button button, gpointer user_data) + { + ui::TreeView view = ui::TreeView::from(lookup_widget(button, "t_materialist")); + ui::ListStore list = ui::ListStore::from(gtk_tree_view_get_model(view)); + auto sel = ui::TreeSelection::from(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(ui::Button button, gpointer user_data) + { + ui::Widget toggleLimit = lookup_widget(button, "t_limitmatnames"); + ui::Widget toggleObject = lookup_widget(button, "t_objects"); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) { + gtk_widget_set_sensitive(toggleLimit, TRUE); + gtk_widget_set_sensitive(toggleObject, TRUE); + } else { + gtk_widget_set_sensitive(toggleLimit, FALSE); + gtk_widget_set_sensitive(toggleObject, FALSE); + } + } + +} // callbacks diff --git a/contrib/brushexport/callbacks.h b/contrib/brushexport/callbacks.h new file mode 100644 index 0000000..6279696 --- /dev/null +++ b/contrib/brushexport/callbacks.h @@ -0,0 +1,16 @@ + +#include + +namespace callbacks { + + void OnDestroy(ui::Widget, gpointer); + + void OnExportClicked(ui::Button, gpointer); + + void OnAddMaterial(ui::Button, gpointer); + + void OnRemoveMaterial(ui::Button, gpointer); + + void OnExportMatClicked(ui::Button button, gpointer); + +} // callbacks diff --git a/contrib/brushexport/export.cpp b/contrib/brushexport/export.cpp new file mode 100644 index 0000000..2235455 --- /dev/null +++ b/contrib/brushexport/export.cpp @@ -0,0 +1,365 @@ +#include "export.h" +#include "globaldefs.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" + +// for limNames +const int MAX_MATERIAL_NAME = 20; + +/* + 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); + +#if GDEF_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); + } + +#if GDEF_DEBUG + globalOutputStream() << "Last: " << (const unsigned int) 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; + + if (path.compare(path.length() - 4, 4, ".obj") != 0) { + objFile += ".obj"; + } + + std::string mtlFile = objFile.substr(0, objFile.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() > MAX_MATERIAL_NAME) { + out << "\nusemtl " << mat.substr(mat.size() - MAX_MATERIAL_NAME, 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 NetRadiants 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() > MAX_MATERIAL_NAME) { + outMtl << "newmtl " << it->substr(it->size() - MAX_MATERIAL_NAME, 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 0000000..e0623dc --- /dev/null +++ b/contrib/brushexport/export.h @@ -0,0 +1,16 @@ +#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 0000000..dd97e63 --- /dev/null +++ b/contrib/brushexport/interface.cpp @@ -0,0 +1,219 @@ +#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, \ + g_object_ref( (void *) widget ), (GDestroyNotify) g_object_unref ) + +#define GLADE_HOOKUP_OBJECT_NO_REF(component, widget, name) \ + g_object_set_data( G_OBJECT( component ), name, (void *) widget ) + +// created by glade +ui::Widget create_w_plugplug2(void) +{ + GSList *r_collapse_group = NULL; + + auto w_plugplug2 = ui::Window(ui::window_type::TOP); + gtk_widget_set_name(w_plugplug2, "w_plugplug2"); + gtk_window_set_title(w_plugplug2, "BrushExport-Plugin 3.0 by namespace"); + gtk_window_set_position(w_plugplug2, GTK_WIN_POS_CENTER); + gtk_window_set_destroy_with_parent(w_plugplug2, TRUE); + + auto vbox1 = ui::VBox(FALSE, 0); + gtk_widget_set_name(vbox1, "vbox1"); + vbox1.show(); + w_plugplug2.add(vbox1); + gtk_container_set_border_width(GTK_CONTAINER(vbox1), 5); + + auto hbox2 = ui::HBox(TRUE, 5); + gtk_widget_set_name(hbox2, "hbox2"); + hbox2.show(); + vbox1.pack_start(hbox2, FALSE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(hbox2), 5); + + auto vbox4 = ui::VBox(TRUE, 0); + gtk_widget_set_name(vbox4, "vbox4"); + vbox4.show(); + hbox2.pack_start(vbox4, TRUE, FALSE, 0); + + auto r_collapse = ui::Widget::from(gtk_radio_button_new_with_mnemonic(NULL, "Collapse mesh")); + gtk_widget_set_name(r_collapse, "r_collapse"); + gtk_widget_set_tooltip_text(r_collapse, "Collapse all brushes into a single group"); + r_collapse.show(); + vbox4.pack_start(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)); + + auto r_collapsebymaterial = ui::Widget::from(gtk_radio_button_new_with_mnemonic(NULL, "Collapse by material")); + gtk_widget_set_name(r_collapsebymaterial, "r_collapsebymaterial"); + gtk_widget_set_tooltip_text(r_collapsebymaterial, "Collapse into groups by material"); + r_collapsebymaterial.show(); + vbox4.pack_start(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)); + + auto r_nocollapse = ui::Widget::from(gtk_radio_button_new_with_mnemonic(NULL, "Don't collapse")); + gtk_widget_set_name(r_nocollapse, "r_nocollapse"); + gtk_widget_set_tooltip_text(r_nocollapse, "Every brush is stored in its own group"); + r_nocollapse.show(); + vbox4.pack_start(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)); + + auto vbox3 = ui::VBox(FALSE, 0); + gtk_widget_set_name(vbox3, "vbox3"); + vbox3.show(); + hbox2.pack_start(vbox3, FALSE, FALSE, 0); + + auto b_export = ui::Button::from(gtk_button_new_from_stock("gtk-save")); + gtk_widget_set_name(b_export, "b_export"); + b_export.show(); + vbox3.pack_start(b_export, TRUE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(b_export), 5); + + auto b_close = ui::Button::from(gtk_button_new_from_stock("gtk-cancel")); + gtk_widget_set_name(b_close, "b_close"); + b_close.show(); + vbox3.pack_start(b_close, TRUE, FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(b_close), 5); + + auto vbox2 = ui::VBox(FALSE, 5); + gtk_widget_set_name(vbox2, "vbox2"); + vbox2.show(); + vbox1.pack_start(vbox2, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 2); + + auto label1 = ui::Label("Ignored materials:"); + gtk_widget_set_name(label1, "label1"); + label1.show(); + vbox2.pack_start(label1, FALSE, FALSE, 0); + + auto scrolledwindow1 = ui::ScrolledWindow(ui::New); + gtk_widget_set_name(scrolledwindow1, "scrolledwindow1"); + scrolledwindow1.show(); + vbox2.pack_start(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); + + auto t_materialist = ui::TreeView(ui::New); + gtk_widget_set_name(t_materialist, "t_materialist"); + t_materialist.show(); + scrolledwindow1.add(t_materialist); + gtk_tree_view_set_headers_visible(t_materialist, FALSE); + gtk_tree_view_set_enable_search(t_materialist, FALSE); + + auto ed_materialname = ui::Entry(ui::New); + gtk_widget_set_name(ed_materialname, "ed_materialname"); + ed_materialname.show(); + vbox2.pack_start(ed_materialname, FALSE, FALSE, 0); + + auto hbox1 = ui::HBox(TRUE, 0); + gtk_widget_set_name(hbox1, "hbox1"); + hbox1.show(); + vbox2.pack_start(hbox1, FALSE, FALSE, 0); + + auto b_addmaterial = ui::Button::from(gtk_button_new_from_stock("gtk-add")); + gtk_widget_set_name(b_addmaterial, "b_addmaterial"); + b_addmaterial.show(); + hbox1.pack_start(b_addmaterial, FALSE, FALSE, 0); + + auto b_removematerial = ui::Button::from(gtk_button_new_from_stock("gtk-remove")); + gtk_widget_set_name(b_removematerial, "b_removematerial"); + b_removematerial.show(); + hbox1.pack_start(b_removematerial, FALSE, FALSE, 0); + + auto t_limitmatnames = ui::Widget::from( + gtk_check_button_new_with_mnemonic("Use short material names (max. 20 chars)")); + gtk_widget_set_name(t_limitmatnames, "t_limitmatnames"); + t_limitmatnames.show(); + vbox2.pack_end(t_limitmatnames, FALSE, FALSE, 0); + + auto t_objects = ui::Widget::from(gtk_check_button_new_with_mnemonic("Create (o)bjects instead of (g)roups")); + gtk_widget_set_name(t_objects, "t_objects"); + t_objects.show(); + vbox2.pack_end(t_objects, FALSE, FALSE, 0); + + auto t_exportmaterials = ui::CheckButton::from( + 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"); + t_exportmaterials.show(); + vbox2.pack_end(t_exportmaterials, FALSE, FALSE, 10); + + using namespace callbacks; + w_plugplug2.connect("destroy", G_CALLBACK(OnDestroy), NULL); + g_signal_connect_swapped(G_OBJECT(b_close), "clicked", G_CALLBACK(OnDestroy), NULL); + + b_export.connect("clicked", G_CALLBACK(OnExportClicked), NULL); + b_addmaterial.connect("clicked", G_CALLBACK(OnAddMaterial), NULL); + b_removematerial.connect("clicked", G_CALLBACK(OnRemoveMaterial), NULL); + t_exportmaterials.connect("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 +ui::Widget g_brushexp_window{ui::null}; + +// spawn plugin window (and make sure it got destroyed first or never created) +void CreateWindow(void) +{ + ASSERT_TRUE(!g_brushexp_window); + + ui::Widget wnd = create_w_plugplug2(); + + // column & renderer + auto col = ui::TreeViewColumn::from(gtk_tree_view_column_new()); + gtk_tree_view_column_set_title(col, "materials"); + auto view = ui::TreeView::from(lookup_widget(wnd, "t_materialist")); + gtk_tree_view_append_column(view, col); + auto renderer = ui::CellRendererText(ui::New); + gtk_tree_view_insert_column_with_attributes(view, -1, "", renderer, "text", 0, NULL); + + // list store + auto ignorelist = ui::ListStore::from(gtk_list_store_new(1, G_TYPE_STRING)); + gtk_tree_view_set_model(view, ignorelist); + ignorelist.unref(); + + gtk_widget_show_all(wnd); + g_brushexp_window = wnd; +} + +void DestroyWindow() +{ + ASSERT_TRUE(g_brushexp_window); + ui::Widget(g_brushexp_window).destroy(); + g_brushexp_window = ui::Widget(ui::null); +} + +bool IsWindowOpen() +{ + return g_brushexp_window; +} diff --git a/contrib/brushexport/plugin.cpp b/contrib/brushexport/plugin.cpp new file mode 100644 index 0000000..25bdb51 --- /dev/null +++ b/contrib/brushexport/plugin.cpp @@ -0,0 +1,135 @@ +/* + 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 "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 { + ui::Window g_mainwnd{ui::null}; + + const char *init(void *hApp, void *pMainWidget) + { + g_mainwnd = ui::Window::from(pMainWidget); + ASSERT_TRUE(g_mainwnd); + return ""; + } + + const char *getName() + { + return "Brush export Plugin"; + } + + const char *getCommandList() + { + return "About;Export selected as Wavefront OBJ"; + } + + const char *getCommandTitleList() + { + return ""; + } + + void dispatch(const char *command, float *vMin, float *vMax, bool bSingleBrush) + { + if (string_equal(command, "About")) { + GlobalRadiant().m_pfnMessageBox(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 OBJ")) { + 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, "Export Brushes"); + + 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 0000000..d924f64 --- /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 0000000..d227b5b --- /dev/null +++ b/contrib/brushexport/support.cpp @@ -0,0 +1,32 @@ +#include +#include + +#include "support.h" + +ui::Widget +lookup_widget(ui::Widget widget, + const gchar *widget_name) +{ + ui::Widget parent{ui::null}; + + for (;;) { + if (GTK_IS_MENU(widget)) { + parent = ui::Widget::from(gtk_menu_get_attach_widget(GTK_MENU(widget))); + } else { + parent = ui::Widget::from(gtk_widget_get_parent(widget)); + } + if (!parent) { + parent = ui::Widget::from(g_object_get_data(G_OBJECT(widget), "GladeParentKey")); + } + if (parent == NULL) { + break; + } + widget = parent; + } + + auto found_widget = ui::Widget::from(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 0000000..374ba75 --- /dev/null +++ b/contrib/brushexport/support.h @@ -0,0 +1,22 @@ +/* + * 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. + */ +ui::Widget lookup_widget(ui::Widget widget, + const gchar *widget_name); diff --git a/contrib/prtview/AboutDialog.cpp b/contrib/prtview/AboutDialog.cpp new file mode 100644 index 0000000..7042359 --- /dev/null +++ b/contrib/prtview/AboutDialog.cpp @@ -0,0 +1,99 @@ +/* + 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 +#include "version.h" +#include "gtkutil/pointer.h" + +#include "prtview.h" +#include "ConfigDialog.h" + +static void dialog_button_callback(ui::Widget widget, gpointer data) +{ + int *loop, *ret; + + auto parent = widget.window(); + 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(ui::Widget widget, GdkEvent *event, gpointer data) +{ + widget.hide(); + int *loop = (int *) g_object_get_data(G_OBJECT(widget), "loop"); + *loop = 0; + return TRUE; +} + +void DoAboutDlg() +{ + int loop = 1, ret = IDCANCEL; + + auto dlg = ui::Window(ui::window_type::TOP); + gtk_window_set_title(dlg, "About Portal Viewer"); + dlg.connect("delete_event", G_CALLBACK(dialog_delete_callback), NULL); + dlg.connect("destroy", G_CALLBACK(gtk_widget_destroy), NULL); + g_object_set_data(G_OBJECT(dlg), "loop", &loop); + g_object_set_data(G_OBJECT(dlg), "ret", &ret); + + auto hbox = ui::HBox(FALSE, 10); + hbox.show(); + dlg.add(hbox); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 10); + + char const *label_text = "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 WorldSpawn " WorldSpawn_VERSION "\n" + __DATE__; + auto label = ui::Label(label_text); + label.show(); + hbox.pack_start(label, TRUE, TRUE, 0); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); + + auto vbox = ui::VBox(FALSE, 0); + vbox.show(); + hbox.pack_start(vbox, FALSE, FALSE, 0); + + auto button = ui::Button("OK"); + button.show(); + vbox.pack_start(button, FALSE, FALSE, 0); + button.connect("clicked", G_CALLBACK(dialog_button_callback), GINT_TO_POINTER(IDOK)); + button.dimensions(60, -1); + + gtk_grab_add(dlg); + dlg.show(); + + while (loop) { + gtk_main_iteration(); + } + + gtk_grab_remove(dlg); + dlg.destroy(); +} + + +///////////////////////////////////////////////////////////////////////////// +// CAboutDialog message handlers diff --git a/contrib/prtview/AboutDialog.h b/contrib/prtview/AboutDialog.h new file mode 100644 index 0000000..b46fa93 --- /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/CMakeLists.txt b/contrib/prtview/CMakeLists.txt new file mode 100644 index 0000000..f08707f --- /dev/null +++ b/contrib/prtview/CMakeLists.txt @@ -0,0 +1,13 @@ +radiant_plugin(prtview + AboutDialog.cpp AboutDialog.h + ConfigDialog.cpp ConfigDialog.h + LoadPortalFileDialog.cpp LoadPortalFileDialog.h + portals.cpp portals.h + prtview.cpp prtview.h + ) + +target_include_directories(prtview PRIVATE uilib) +target_link_libraries(prtview PRIVATE uilib) + +target_include_directories(prtview PRIVATE profile) +target_link_libraries(prtview PRIVATE profile) diff --git a/contrib/prtview/ConfigDialog.cpp b/contrib/prtview/ConfigDialog.cpp new file mode 100644 index 0000000..7784bee --- /dev/null +++ b/contrib/prtview/ConfigDialog.cpp @@ -0,0 +1,481 @@ +/* + 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 +#include "gtkutil/pointer.h" + +#include "iscenegraph.h" + +#include "prtview.h" +#include "portals.h" + +static void dialog_button_callback(ui::Widget widget, gpointer data) +{ + int *loop, *ret; + + auto parent = widget.window(); + 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(ui::Widget widget, GdkEvent *event, gpointer data) +{ + widget.hide(); + int *loop = (int *) g_object_get_data(G_OBJECT(widget), "loop"); + *loop = 0; + return TRUE; +} + +// ============================================================================= +// Color selection dialog + +static int DoColor(PackedColour *c) +{ + GdkColor clr; + int loop = 1, ret = IDCANCEL; + + clr.red = (guint16) (GetRValue(*c) * (65535 / 255)); + clr.blue = (guint16) (GetGValue(*c) * (65535 / 255)); + clr.green = (guint16) (GetBValue(*c) * (65535 / 255)); + + auto dlg = ui::Widget::from(gtk_color_selection_dialog_new("Choose Color")); + gtk_color_selection_set_current_color( + GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(dlg))), &clr); + dlg.connect("delete_event", G_CALLBACK(dialog_delete_callback), NULL); + dlg.connect("destroy", G_CALLBACK(gtk_widget_destroy), NULL); + + GtkWidget *ok_button, *cancel_button; + g_object_get(dlg, "ok-button", &ok_button, "cancel-button", &cancel_button, nullptr); + + ui::Widget::from(ok_button).connect("clicked", G_CALLBACK(dialog_button_callback), GINT_TO_POINTER(IDOK)); + ui::Widget::from(cancel_button).connect("clicked", G_CALLBACK(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); + + dlg.show(); + gtk_grab_add(dlg); + + while (loop) { + gtk_main_iteration(); + } + + gtk_color_selection_get_current_color( + GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(dlg))), &clr); + + gtk_grab_remove(dlg); + dlg.destroy(); + + if (ret == IDOK) { + *c = RGB(clr.red / (65535 / 255), clr.green / (65535 / 255), clr.blue / (65535 / 255)); + } + + return ret; +} + +static void Set2DText(ui::Widget 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(ui::Widget 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(ui::Widget label) +{ + char s[40]; + + sprintf(s, "Polygon transparency = %d%%", (int) portals.trans_3d); + + gtk_label_set_text(GTK_LABEL(label), s); +} + +static void SetClipText(ui::Widget 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(ui::Adjustment adj, gpointer data) +{ + portals.width_2d = static_cast( gtk_adjustment_get_value(adj)); + Set2DText(ui::Widget::from(data)); + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnScroll3d(ui::Adjustment adj, gpointer data) +{ + portals.width_3d = static_cast( gtk_adjustment_get_value(adj)); + Set3DText(ui::Widget::from(data)); + + SceneChangeNotify(); +} + +static void OnScrollTrans(ui::Adjustment adj, gpointer data) +{ + portals.trans_3d = static_cast( gtk_adjustment_get_value(adj)); + Set3DTransText(ui::Widget::from(data)); + + SceneChangeNotify(); +} + +static void OnScrollClip(ui::Adjustment adj, gpointer data) +{ + portals.clip_range = static_cast( gtk_adjustment_get_value(adj)); + SetClipText(ui::Widget::from(data)); + + SceneChangeNotify(); +} + +static void OnAntiAlias2d(ui::Widget widget, gpointer data) +{ + portals.aa_2d = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ? true : false; + + Portals_shadersChanged(); + + SceneChangeNotify(); +} + +static void OnConfig2d(ui::Widget widget, gpointer data) +{ + portals.show_2d = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ? true : false; + + SceneChangeNotify(); +} + +static void OnColor2d(ui::Widget widget, gpointer data) +{ + if (DoColor(&portals.color_2d) == IDOK) { + Portals_shadersChanged(); + + SceneChangeNotify(); + } +} + +static void OnConfig3d(ui::Widget widget, gpointer data) +{ + portals.show_3d = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ? true : false; + + SceneChangeNotify(); +} + + +static void OnAntiAlias3d(ui::Widget widget, gpointer data) +{ + portals.aa_3d = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ? true : false; + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnColor3d(ui::Widget widget, gpointer data) +{ + if (DoColor(&portals.color_3d) == IDOK) { + Portals_shadersChanged(); + + SceneChangeNotify(); + } +} + +static void OnColorFog(ui::Widget widget, gpointer data) +{ + if (DoColor(&portals.color_fog) == IDOK) { + Portals_shadersChanged(); + + SceneChangeNotify(); + } +} + +static void OnFog(ui::Widget widget, gpointer data) +{ + portals.fog = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ? true : false; + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnSelchangeZbuffer(ui::Widget widget, gpointer data) +{ + portals.zbuffer = gpointer_to_int(data); + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnPoly(ui::Widget widget, gpointer data) +{ + portals.polygons = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + + SceneChangeNotify(); +} + +static void OnLines(ui::Widget widget, gpointer data) +{ + portals.lines = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + + SceneChangeNotify(); +} + +static void OnClip(ui::Widget widget, gpointer data) +{ + portals.clip = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) ? true : false; + + SceneChangeNotify(); +} + +void DoConfigDialog() +{ + int loop = 1, ret = IDCANCEL; + + auto dlg = ui::Window(ui::window_type::TOP); + gtk_window_set_title(dlg, "Portal Viewer Configuration"); + dlg.connect("delete_event", + G_CALLBACK(dialog_delete_callback), NULL); + dlg.connect("destroy", + G_CALLBACK(gtk_widget_destroy), NULL); + g_object_set_data(G_OBJECT(dlg), "loop", &loop); + g_object_set_data(G_OBJECT(dlg), "ret", &ret); + + auto vbox = ui::VBox(FALSE, 5); + vbox.show(); + dlg.add(vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); + + auto frame = ui::Frame("3D View"); + frame.show(); + vbox.pack_start(frame, TRUE, TRUE, 0); + + auto vbox2 = ui::VBox(FALSE, 5); + vbox2.show(); + frame.add(vbox2); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); + + auto hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox2.pack_start(hbox, TRUE, TRUE, 0); + + auto adj = ui::Adjustment(portals.width_3d, 2, 40, 1, 1, 0); + auto lw3slider = ui::HScale(adj); + lw3slider.show(); + hbox.pack_start(lw3slider, TRUE, TRUE, 0); + gtk_scale_set_draw_value(GTK_SCALE(lw3slider), FALSE); + + auto lw3label = ui::Label(""); + lw3label.show(); + hbox.pack_start(lw3label, FALSE, TRUE, 0); + adj.connect("value_changed", G_CALLBACK(OnScroll3d), lw3label); + + auto table = ui::Table(2, 4, FALSE); + table.show(); + vbox2.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + + auto button = ui::Button("Color"); + button.show(); + table.attach(button, {0, 1, 0, 1}, {GTK_FILL, 0}); + button.connect("clicked", G_CALLBACK(OnColor3d), NULL); + + button = ui::Button("Depth Color"); + button.show(); + table.attach(button, {0, 1, 1, 2}, {GTK_FILL, 0}); + button.connect("clicked", G_CALLBACK(OnColorFog), NULL); + + auto aa3check = ui::CheckButton("Anti-Alias (May not work on some video cards)"); + aa3check.show(); + table.attach(aa3check, {1, 4, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + aa3check.connect("toggled", G_CALLBACK(OnAntiAlias3d), NULL); + + auto depthcheck = ui::CheckButton("Depth Cue"); + depthcheck.show(); + table.attach(depthcheck, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + depthcheck.connect("toggled", G_CALLBACK(OnFog), NULL); + + auto linescheck = ui::CheckButton("Lines"); + linescheck.show(); + table.attach(linescheck, {2, 3, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + linescheck.connect("toggled", G_CALLBACK(OnLines), NULL); + + auto polyscheck = ui::CheckButton("Polygons"); + polyscheck.show(); + table.attach(polyscheck, {3, 4, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + polyscheck.connect("toggled", G_CALLBACK(OnPoly), NULL); + + auto zlist = ui::ComboBoxText(ui::New); + zlist.show(); + vbox2.pack_start(zlist, TRUE, FALSE, 0); + + gtk_combo_box_text_append_text(zlist, "Z-Buffer Test and Write (recommended for solid or no polygons)"); + gtk_combo_box_text_append_text(zlist, "Z-Buffer Test Only (recommended for transparent polygons)"); + gtk_combo_box_text_append_text(zlist, "Z-Buffer Off"); + + zlist.connect("changed", G_CALLBACK(+[](ui::ComboBox self, void *) { + OnSelchangeZbuffer(self, GINT_TO_POINTER(gtk_combo_box_get_active(self))); + }), nullptr); + + table = ui::Table(2, 2, FALSE); + table.show(); + vbox2.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + + adj = ui::Adjustment(portals.trans_3d, 0, 100, 1, 1, 0); + auto transslider = ui::HScale(adj); + transslider.show(); + table.attach(transslider, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + gtk_scale_set_draw_value(GTK_SCALE(transslider), FALSE); + + auto translabel = ui::Label(""); + translabel.show(); + table.attach(translabel, {1, 2, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(translabel), 0.0, 0.0); + adj.connect("value_changed", G_CALLBACK(OnScrollTrans), translabel); + + adj = ui::Adjustment(portals.clip_range, 1, 128, 1, 1, 0); + auto clipslider = ui::HScale(adj); + clipslider.show(); + table.attach(clipslider, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + gtk_scale_set_draw_value(GTK_SCALE(clipslider), FALSE); + + auto cliplabel = ui::Label(""); + cliplabel.show(); + table.attach(cliplabel, {1, 2, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(cliplabel), 0.0, 0.0); + adj.connect("value_changed", G_CALLBACK(OnScrollClip), cliplabel); + + hbox = ui::HBox(TRUE, 5); + hbox.show(); + vbox2.pack_start(hbox, TRUE, FALSE, 0); + + auto show3check = ui::CheckButton("Show"); + show3check.show(); + hbox.pack_start(show3check, TRUE, TRUE, 0); + show3check.connect("toggled", G_CALLBACK(OnConfig3d), NULL); + + auto portalcheck = ui::CheckButton("Portal cubic clipper"); + portalcheck.show(); + hbox.pack_start(portalcheck, TRUE, TRUE, 0); + portalcheck.connect("toggled", G_CALLBACK(OnClip), NULL); + + frame = ui::Frame("2D View"); + frame.show(); + vbox.pack_start(frame, TRUE, TRUE, 0); + + vbox2 = ui::VBox(FALSE, 5); + vbox2.show(); + frame.add(vbox2); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); + + hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox2.pack_start(hbox, TRUE, FALSE, 0); + + adj = ui::Adjustment(portals.width_2d, 2, 40, 1, 1, 0); + auto lw2slider = ui::HScale(adj); + lw2slider.show(); + hbox.pack_start(lw2slider, TRUE, TRUE, 0); + gtk_scale_set_draw_value(GTK_SCALE(lw2slider), FALSE); + + auto lw2label = ui::Label(""); + lw2label.show(); + hbox.pack_start(lw2label, FALSE, TRUE, 0); + adj.connect("value_changed", G_CALLBACK(OnScroll2d), lw2label); + + hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox2.pack_start(hbox, TRUE, FALSE, 0); + + button = ui::Button("Color"); + button.show(); + hbox.pack_start(button, FALSE, FALSE, 0); + button.connect("clicked", G_CALLBACK(OnColor2d), NULL); + button.dimensions(60, -1); + + auto aa2check = ui::CheckButton("Anti-Alias (May not work on some video cards)"); + aa2check.show(); + hbox.pack_start(aa2check, TRUE, TRUE, 0); + aa2check.connect("toggled", G_CALLBACK(OnAntiAlias2d), NULL); + + hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox2.pack_start(hbox, TRUE, FALSE, 0); + + auto show2check = ui::CheckButton("Show"); + show2check.show(); + hbox.pack_start(show2check, FALSE, FALSE, 0); + show2check.connect("toggled", G_CALLBACK(OnConfig2d), NULL); + + hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox.pack_start(hbox, FALSE, FALSE, 0); + + button = ui::Button("OK"); + button.show(); + hbox.pack_end(button, FALSE, FALSE, 0); + button.connect("clicked", + G_CALLBACK(dialog_button_callback), GINT_TO_POINTER(IDOK)); + button.dimensions(60, -1); + + // 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_combo_box_set_active(zlist, portals.zbuffer); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(portalcheck), portals.clip); + + Set3DText(lw3label); + Set3DTransText(translabel); + SetClipText(cliplabel); + + gtk_grab_add(dlg); + dlg.show(); + + while (loop) { + gtk_main_iteration(); + } + + gtk_grab_remove(dlg); + dlg.destroy(); +} diff --git a/contrib/prtview/ConfigDialog.h b/contrib/prtview/ConfigDialog.h new file mode 100644 index 0000000..a972584 --- /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 0000000..f83b34f --- /dev/null +++ b/contrib/prtview/LoadPortalFileDialog.cpp @@ -0,0 +1,169 @@ +/* + 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(ui::Widget widget, gpointer data) +{ + int *loop, *ret; + + auto parent = widget.window(); + 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(ui::Widget widget, GdkEvent *event, gpointer data) +{ + widget.hide(); + int *loop = (int *) g_object_get_data(G_OBJECT(widget), "loop"); + *loop = 0; + return TRUE; +} + +static void change_clicked(ui::Widget widget, gpointer data) +{ + char *filename = NULL; + + auto file_sel = ui::Widget::from( + gtk_file_chooser_dialog_new("Locate portal (.prt) file", nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + nullptr)); + + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_sel), portals.fn); + + if (gtk_dialog_run(GTK_DIALOG (file_sel)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_sel)); + } + ui::Widget(file_sel).destroy(); + + if (filename != NULL) { + strcpy(portals.fn, filename); + gtk_entry_set_text(GTK_ENTRY(data), filename); + g_free(filename); + } +} + +int DoLoadPortalFileDialog() +{ + int loop = 1, ret = IDCANCEL; + + auto dlg = ui::Window(ui::window_type::TOP); + gtk_window_set_title(dlg, "Load .prt"); + dlg.connect("delete_event", + G_CALLBACK(dialog_delete_callback), NULL); + dlg.connect("destroy", + G_CALLBACK(gtk_widget_destroy), NULL); + g_object_set_data(G_OBJECT(dlg), "loop", &loop); + g_object_set_data(G_OBJECT(dlg), "ret", &ret); + + auto vbox = ui::VBox(FALSE, 5); + vbox.show(); + dlg.add(vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); + + auto entry = ui::Entry(ui::New); + entry.show(); + gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE); + vbox.pack_start(entry, FALSE, FALSE, 0); + + auto hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox.pack_start(hbox, FALSE, FALSE, 0); + + auto check3d = ui::CheckButton("Show 3D"); + check3d.show(); + hbox.pack_start(check3d, FALSE, FALSE, 0); + + auto check2d = ui::CheckButton("Show 2D"); + check2d.show(); + hbox.pack_start(check2d, FALSE, FALSE, 0); + + auto button = ui::Button("Change"); + button.show(); + hbox.pack_end(button, FALSE, FALSE, 0); + button.connect("clicked", G_CALLBACK(change_clicked), entry); + button.dimensions(60, -1); + + hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox.pack_start(hbox, FALSE, FALSE, 0); + + button = ui::Button("Cancel"); + button.show(); + hbox.pack_end(button, FALSE, FALSE, 0); + button.connect("clicked", + G_CALLBACK(dialog_button_callback), GINT_TO_POINTER(IDCANCEL)); + button.dimensions(60, -1); + + button = ui::Button("OK"); + button.show(); + hbox.pack_end(button, FALSE, FALSE, 0); + button.connect("clicked", + G_CALLBACK(dialog_button_callback), GINT_TO_POINTER(IDOK)); + button.dimensions(60, -1); + + strcpy(portals.fn, GlobalRadiant().getMapName()); + char *fn = strrchr(portals.fn, '.'); + if (fn != NULL) { + strcpy(fn, ".prt"); + } + + StringOutputStream value(256); + value << 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); + dlg.show(); + + 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); + dlg.destroy(); + + return ret; +} diff --git a/contrib/prtview/LoadPortalFileDialog.h b/contrib/prtview/LoadPortalFileDialog.h new file mode 100644 index 0000000..62dfc3d --- /dev/null +++ b/contrib/prtview/LoadPortalFileDialog.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_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 zcmd5^36vv8dH&bC;3M9cm;?w(h;W>+`@pukCCx~jZuK$KSxGHRt(l!=L2tY>yPmL~ zS!QSP5hw9Q-h+^ngq(m0U~?E^bKh4u41@p)1js=EbNIILg@M52`~Rv|Rkt*onD_Et zG*2U`zOVlJ>;CJn>LMa4;k~)3-uRk()$r>~yk}{in)uw@+@|qUcjzy4L7&Kf>i!GX zE?nLpFYTY%cwTRDX=QPBWB-A*)s6L)^Yl<_UJbV2B$Nee1t9Yjr z(q8qed|kGW%UW7KxU#xj8hX**VJ8{(yRA4)!``qL_LD)+5Bob$ z`IQ1EX@$pw)1K$=IlhE$FX@K8bio#bbXE?Qo~Y7V+Qs^0It+tWYj^3g%StwwdcB!p z*ct4Wh(Joi`M6VYcb?MnIx_=rCM-3cQgVPESzkPK;bQ5)+S2my$l{^pU5;CGIok5- z(#k=Oo|PvG)2d(hj8f^Gb4sggt7o-W4y>;oT|2l@np-(~Z1Hf(fAW({%S$U8%S)xK zI*;0tbj_?EfDHYDsQ$*{>eAx+5_Gxn$lB`i>IO#=HBd9>qIydB+}wQBsEwA%w3~Cc zbM4MipN9A!QV-u^O3>nl((WRB=hypp-2G!BZsS=ymtcC9`XFwIs#WO#d|ZQoZIHJ? z>$F0vbciB+9;RhTUZsPy2F_)|DWi>*seyNu&c}bE$KkyVt$vLz#NRUb8*~(5B`f?0 zEh799=(;EzK?qk4jfqO`p zoa-aV7nj;0x&Ts$W{}GPw8}6p#e<^tE`;p{;*Qea1->mWg(S}DGD>L?-=8NXiMBzf zf|uxYxQ00AS=sR~E_v3EOC{6yY>YxJ{1MXRbQoQs7l6&VZPGFPI*imf9@hoqYxsQ( z^{|nvtA|?$WvPR_ykbq*^`P|Yqb!$^HrMmvoPL4mKZo%3QLatP*d>DyE}Y;HaR{p$+w^W*A==%&j#O!bZT$XH6`p_Q+pME<8%cM`SHe@b|B z8$BxDxIM0*?z!x@by@Zm^eEK+PviII=H;l*Jsc>n69hkzPDDz-26S`tO4K7u$l7j+ zOo{EJotVv7`Ud{E&y}F(CHOysUu#J7AkbwA;hr@wTJ1y|<+gQ^{5lt<&8=-kDslNeQht0*<~~BpTZ8;1Xmz%v#l7PsW^6lNS&0ZH zq0FvBPf_K?<6^$V6puNN5zg%xKbhmBA95WWLVoPmcE~%C9*cV2Nw;clAKW#RM+svL zw=w(0d>{O0fPc#v-yP#ajdzTAeCF09D`XmZ$yX7Gd)pv#H$dGzbsM7Ap1XQCOG zBrdnnt0azH-dP)ctRRovwgdIUy?3k8P>n)7g7f%vOzPW=t^d0=`x&}>Vw*jY6UK}4 z@eutP^yM6LIhqlfalC|g8vQK&5uHN!ZQtCyT7^qF@Ym~r^?=`FRIqpHuGhB1@d zz=OcJcGpmroL_!%9r5RR=reDopP>hY(XQ^v7N3p^MTZRuP~3^F<=( zdHBY2O$jWX!F+zMdFqaPdCO*v?_$CQ2)2TanI^x$8_5W-^gZ1e)qBsbKUNae_NNn z(s<~#Vy~~2uft%|3AB=)4s2kik-&Y;K@^F8PMJCK z#A?tlsK8d-2|1oyreDm-^ast@XTYIfQotz0r6=f+R@`Muh0ueBUbjE&gnLu7qM!3x z1Jrh2xsU{!~4n4`n`wQ(biw=~qzRH?)<}6{m z@`-fO%Br|dVC`?4>ksF`UO!Gc!?+VAn0Qa8ZBmG-mm;gX&T@8R?nf++>oi4q9P5?DygNRH%bR z*(m00*y^^EAkT2=&kbWFdlf@SIS|~4ZKum!lO4Cfb#}Br@Ju9NfRrWQM`VLDYEUw(g>ZhPk(2yc5eXTy}r^1 zR>_S;tQ9(v6<}`}$KaYo;b2;Q>o`_Xm`iUP$Epge(A&qcn!>8|j&W>CVKsW^I5w@Y zDSFp9R#(_Gy?Y#kbDHx|r}vCw$hCkCdhY~KmyAd6n*i#fY0~>AfVymatVc)nr0N3Y ztqUih4^9Ae>4fy537{^Xh(4SLTV7Vf5q(6UFo@Hk-wHh$e;inCA1j2zXi+T&hTF$2 z4DC$H#C7QtRxm7nE-9FBH<{1~G)O;ep(~nH)Avhb zs2Bx;R_V(&+UI>aQNB{6uL#1!wOW{Qgld_-J_-)^YI4<~Z%8PQ{yZm!dqqgqrJGGS zSE|@J5L2OV8eq`ZbAVH=(zhg>4RE&PS)dYCqi>I5sE{eTMM8ow@?hOr+-bU15Lpah z8c_vRE7Khk%tPhS1Uq!61V>4)4NEaX(vrYbneGvQcOv6XAC^>BS+H{Hz7ZyUf?J== z2QJnF_vbO>CM$9m)5`+|be8LR7=y*>x|j$b%ro>xW{icB5UTXO5dngmW+-+s@H~`F zlS4fxpug(LtpdG&JO;xC;aQ7iSD^gnI6)gX6(~>J^jtrZ&Z(%l6@cR?raT2#>4Y(O zUy`(A$#(@)<@Sje>egiI>~^&a1>sp zlTElcNE55e)uGKP2JN>os%14)MYiR8dV6z3E%nio4(to8 z^GsuEpjaPkBiVG&Kr4Bi4RA-}Dr%sC{JafhZKHuk{dfl8GO$*=XJp#-;Yr|C6$`;) zkDhD;?L^sN5t4mM4&ZE$V!3oy4&&}tZ5zhqX~U!b6v5Bq z*a*%Nz?fFqK$W{TmU{g?E!`d>)Oy8$ZLA_@_RUK?(%t&u-C&9%bP=|@tQiXFg=2d z1iw!LyL!;=CcP91gnRw4o5Ir~;$Vn`hB-R!Q6RY#8B#lJqqvK5Dl}s#155}-LX~C> zq07d8(qWZE4a}W;44QPX^pZh0Dr5d^86=%g4B}GTAR>`5wQ5Af3UzE)mHT<~s(h(V9VfX_ON_MMn%MmZ+7x#xy0mO)POrs%gP@mk)XU6oG zWd8pgqd`{~hR&Rx2GFasym@q`VGTOHa0dC71qKpn(p5z;mxNDO8yu$%)%KN2OXWDA z7v>pF>~CR6<6Q&A2EW8a(Z46C&T6d+YTxlFk%0oNNO&~;vN#F=nsqFV3f&e z`lBLf_bYbD{Be*lWJ&eqXRi(ciW1(o3fnS>mSXuMJOG=0i0R zOLCZhUtw@vR2UMlRP88dWaQKxdZppOC_-~ZRrh5ebm}f$V^H0iWrB7v1-~kTlmUkk z#7?|Qf1BZ$Jx0R>OuabL(rfhUNfb3gs4Ps;YYasXX(N{?)Ssr;8r-B}Q|ff>Bnmn| z%z0%)JM{)#H;Kfi1!~!5di1(UOyebhR!w@n;mm~aJlF=V%9&4ZFdR6QCvgILqv7b> zqjmaTPc^%GNY@*J$-WxXRXP#9X^esiJ(l51D45T0FqEt>2?xWRqBwM;q3DE!!G2~% zap^424f^omj1y9Ir<2E|9VU$t+*To1*s=I6bU5*c~rcgc{GiX?kyg zg-rl6u6T8NUjgSl^}~?;peU3Ey}y9ZqTYwG*t(AA(FY1}AGq?xA$F5KSb#AL?is;8 zeW(EQs5ct)FsyyJfQLz>^a|-C2J(8C%SZLxEYn8|m|f3Jhdx$-dC*HbIBLLIZo2gG z0*=;|M5B^#R_GH2f((a7LX~bR5KyR@PZKHB=#%5P57&rT&Q5cRJ~fW_{n@b1dv6dh zO`pyP{bAzsAquY*4mxq#A9k^CEPkQL@yqlX!_z)3?VOBUc#HqZpn6Lj?OmAyE*!@H zY+%Utu_tH2E`7nk>^-uu3Vkudy2Ji?t!%WHA(*q^V#xouJVm*VcnpG_JkO0RD^M3R zz`y6oVY`cmGW?`9Xyb5$@`ylah5jSYi{h|_<}5`wY{zhFrM^tMZoNuh&2!^cE1ZF? z!h4TO#~S@-o+3v8x)@Y?$vn0=aiYeDuRxKf=xcd0oDRAxV(@0SnBq;-*T;Ah#MS8= zd73F7ySciUG;YolxxA5fnxx8ZbL$>`Gfzo^rat(jMp30%lfIRw@%e;_lY(3K>Dzg3 zzk@C6SqzwCvy590=sS5bkNlXcWI@!C(-Ek=kZ#Gdbi>KBBDyutLRMkCd1y6ypms5p z+?MB{e202{Jg#2Dq1*FxDV{kO)pp(hTc)bw8ZOlgHx@8Xa0qI^_IF6=T`? z-8@N`N`UPstiP<*faT}Cd8$6G!i^e^#7X&R(0%zRs{7@pQ5UPx`}3@3lI&@Fy*&jN zoYJ^S4;+t;B`myox`=#wFyr=zDoYv(==&McMRS3*+nIrz9P1ubAJ*RcaHvMDD_l$; zn|W@4BMnUq-=Ywv59~pxQw~a$_BwGc^~r$9#U!#V&q8fsh(!-kt~?jB$RqO%%n_W5 z9PHvDvRS1Q^9(iK4LUh5riR@e&Zen3coDe)_7Vypj}6*$=LXnQD8M}bbHfj1 z+W``=xlo|MAf3&3jsP1C1roY#6Gt+YkIN0P;ZPvtn?*21rxyr%As`DADf?iW9$nzD zkhqN@8IG;2s|0oW*^Gu^4zoiu*^4uNWqP~`R^B{>I`j)BR6Galh7ivP@54Qz2nvPW z18~CdR@^U-5m3b%tx#zU)xKA^T%{+Tz*|w+9&O>2O2T`%*dBA}ObMQk_cif+q6)<6 z!z@s5lz>^KU&{iqF&g)IVnc9^o@5eDy(W*ed=yckW!jaY0q;e!H!En(P^CPLq@QLm z$!CVZyKf`nBxaY4Y@cyd`1lP$?R4sO7^77>*PyE8Ww(p58hI9`w>cS@qNV}FJD2Ui zGdfMaLD{VuY8jz7+IZ%*S4-e&IzNMBY%t>3QlNC83Qm?(XgQCHdIDAHUebnhu{J%)3G8pKg;eTU=F>Yh_QhL#HEXh$V@N9@DHp) z7ZEPv z2&hiKRfKRvdN$4Q2K{ys4`7_AT*5#4JCo2Xr*M&8J_*;kg@5#lA}opX2*rwHPxs0q zfm1iRhHvz$NerEHctx)+z^Xm6OZ0^X=`XvC}9{&x+?F=PgV1Jj`wSwQTk z><>q{OD{IzdVoc6g?`Tjt3zH0twAE5*lcbJU zD_TUaE7DN9Y7pi+Lce@{k)R$dLFvhw496_#4Mj>b?L=7(qHn%&0uFx`j@XG7=$zM2 zV2pB%uKA`3_$)>j>EudCjow@&!RsZf7$a$l{;o(Wh?%AvwqzJNb-K~8;6ff?3r(rf zptl&LnaIHrl%q#)HNi1F2+BJeI!$_;0hn)tKE2(5YMwDV1oRFABCB{H0)vn2_+-we z&MISV<8kM?Te(nQUojx=H8(QZkLL4{! zu;Fk)4@c2G`p7txkKLrdHxylhMu1NrorvaT1oW{nmYOND2LmFU7Nn0GlIrqe4GsDD zGjNAKF&XC*(7;`~$>4l)W5|mE!EjpU(*=wt035oiU&jhIf3R@Wek^`}E6sZkpQ3N{}U$XHao{M=~xVf+|?1@J|#3sg< zZ3;}xBvQTAOm<$CzA}M?xz6I$=wEFP+zPxDW3wXZ;qXyYZF+^+$M`p!C!{1DdGkVS zVf?#I(1J2AZHO(5|FCfu;I+7T7e=*+ZH%wloB#`0Ka7)+Yazr=#(&xjOA5}t^iws5 zU_;|;HVe8aw=6O?qjqyc&!?~39D7THcWS?3gChZUiG*}>fs>V-s>O)DY2#>XyvM{G zr6oLA-?9lRw*|LR2uIepZ5H|k9OT>_%uZAYKh}3_5@!Z37b#s)25zidYy$6wN!iJH zLsPzRX5CukL6E6tc(HD?33|f|^7t%If11Ii3Rm+C-?DYgYG&Gkq`YYjP`e6ZPDE}8!mKYb#%1QW5;fGh9_(`>7L_| zRhE7FuFdI$VUQ_VOgo@^ZK_IJ>J|klF3hkQ(tS1ud&9@kFQWU8Ll*t8&-8%J$#lzA zuadE|2Jn( z^b-!f6rXGVq&&rLKiAIplJM))9X>r35A5-cFMP|#e%x`eg#R5YpA%7)K$K??2+Zgf{Sf3A{D%=Wwq`1JB`d%n09EfBUw%a}etwU92_x z|DexB(0nzg&&zOmME)LvPmR?VHxul#v-J(>H~nP6=B5;Z zx|3ma6~cX@-{|N4@>5yn(EdUBXy(-+S;q`OV)Rl5gy%>*MMiPEGDQ_{8YK;8yN0Tk<=p+xr~hNk+@4Jqn~$c3HsWI@5)arBdCmf zG=+w|erwItr3yb%6uENY$Lc#@t*|N81b+&yA&{-DnE}q|tBhxT!-b{J52$ei z`$?Gn4Phyyv)a zv@X^?n|vSU)A8Oq9lpsXe+L}j!FUTqZFL7D-yt`POHZ=751&YG29Fx!Y zd)n)@huEdN|$bF#7AaQ+)q>G_=i literal 0 HcmV?d00001 diff --git a/contrib/prtview/PrtView.def b/contrib/prtview/PrtView.def new file mode 100644 index 0000000..7c18cf8 --- /dev/null +++ b/contrib/prtview/PrtView.def @@ -0,0 +1,7 @@ +; PrtView.def : Declares the module parameters for the DLL. + +LIBRARY "PrtView" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/contrib/prtview/PrtView.txt b/contrib/prtview/PrtView.txt new file mode 100644 index 0000000..50228e0 --- /dev/null +++ b/contrib/prtview/PrtView.txt @@ -0,0 +1,12 @@ +Put PrtView.dll in the Q3Radiant plugins directory. + +This program is pretty self explanitary, but point needs to +be mentioned. In the configuration menu for 3D view options, +the lines and polygons flags are tri-state. In the third state, +the lines or polygons will only be drawn if the have the +hint flag set. Older version of q3map will not set this flag +and the hint shader may have to be modified to set it. As of +this writing, I do not know all the details. + +Geoffrey DeWan +gdewan@prairienet.org \ No newline at end of file diff --git a/contrib/prtview/portals.cpp b/contrib/prtview/portals.cpp new file mode 100644 index 0000000..c4d3b04 --- /dev/null +++ b/contrib/prtview/portals.cpp @@ -0,0 +1,639 @@ +/* + 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 "globaldefs.h" +#include +#include + +#if !GDEF_OS_MACOS + +#include + +#endif + +#include + +#include "iglrender.h" +#include "cullable.h" + +#include "prtview.h" + +const int 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", (int *) &test_vals_1, (int *) &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 (unsigned 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; + } + if (clip.min[1] < portals.portal[n].min[1]) { + continue; + } + if (clip.min[2] < portals.portal[n].min[2]) { + continue; + } + if (clip.max[0] > portals.portal[n].max[0]) { + continue; + } + if (clip.max[1] > portals.portal[n].max[1]) { + continue; + } + if (clip.max[2] > portals.portal[n].max[2]) { + continue; + } + } + + glBegin(GL_LINE_LOOP); + + for (unsigned 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 0000000..b3d2905 --- /dev/null +++ b/contrib/prtview/portals.h @@ -0,0 +1,162 @@ +/* + 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); +}; + +#ifdef PATH_MAX +const int PRTVIEW_PATH_MAX = PATH_MAX; +#else +const int PRTVIEW_PATH_MAX = 260; +#endif +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 0000000..e99b176 --- /dev/null +++ b/contrib/prtview/prtview.cpp @@ -0,0 +1,323 @@ +/* + 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 + +const char *RENDER_2D = "Render2D"; +const char *WIDTH_2D = "Width2D"; +const char *AA_2D = "AntiAlias2D"; +const char *COLOR_2D = "Color2D"; + +const char *RENDER_3D = "Render3D"; +const char *WIDTH_3D = "Width3D"; +const char *AA_3D = "AntiAlias3D"; +const char *COLOR_3D = "Color3D"; +const char *COLOR_FOG = "ColorFog"; +const char *FOG = "Fog"; +const char *ZBUFFER = "ZBuffer"; +const char *POLYGON = "Polygons"; +const char *LINE = "Lines"; +const char *TRANS_3D = "Transparency"; +const char *CLIP_RANGE = "ClipRange"; +const char *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)"); +} + + +const char *CONFIG_SECTION = "Configuration"; + +int INIGetInt(const 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(const char *key, int val, const 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 WorldSpawn"; +} + +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, "PRT Viewer"); + + 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 0000000..2253474 --- /dev/null +++ b/contrib/prtview/prtview.h @@ -0,0 +1,37 @@ +/* + 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(const char *key, int def); + +void INISetInt(const char *key, int val, const char *comment = 0); + +const int IDOK = 1; +const int IDCANCEL = 2; + + +#endif diff --git a/contrib/prtview/res/PrtView.rc2 b/contrib/prtview/res/PrtView.rc2 new file mode 100644 index 0000000..14acdde --- /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/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1a2b03b32a536d0df3b4c493101fba0fa73929ac GIT binary patch literal 65969 zcmV(uK zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3^ivh2E!UH{dJYJy1d*&LFhBHRNv-(xWMIrmXz zyBy)W@7a&Fa%D;+Ffk_xK-d1C|Kq#<<3Ii*lslv=<=RRw<>r5>ryd7?Y4`7c(9&ny!hY$ zohqyMW$IaYOiy- z?Vi8;`^PuV*To-`h_dngvpZ~t^ZQr+lcQ;aD3(6nUJZlvGdK+HInxyCaSm9UY zzs%q3`PKQ=u=a(Cy!T@LCV%fCvW?#rQh37%U)bl*6&9D6;*OO+V_fk$Uu#c|Judb% zJjq|-i}mTx`!sfU#eH!r>4|?|OE{m~&*!bs_~bY6(inKNz>oZIe_j8lZ~njhy1q{z z3E^&kuNBu7^&&3Ao%45I#fF6M&%Wiez<>Svv;Fr!*$<}84(7AWjT^)ue{V5!_=jyh zXP+Nlm-+MOo8hYOpEqEM`0UKZgohotOL%f9;jS^(5Lm~?1`Vz}J}FLiAmkihE;6R) zDeuBF!6YOkX`Ew$WAtF5)(MvwggOmi>2_SSnJBVL0K8obxw^@Fd>IG>qj zo@LhAW}jnWJ}a%f%BriazQ!Bh=LJ{Z^47P#{T(}88)(^S=UsN)ZTCGsZ0&o#^yRO7 z^=n`M#^1a4XIKBzum6W#3x9Sko}Kb__3vHdt5>amy+sgCvU|pk#T3}_>JDJg(LK9c zNGZB=?%CZVPLU%QncbV~4el6Qn0AQy3xDt3KX&fFwQpB$|E+zC|F@mH+`9iCJ9n{l z|9Rj3i(PvSPwVX1&kJ4C`@#FL?fs=Ihm@bt_@gI+21d_cCR^b*ZbN2BYbUT=+iexez4BA+svi5v6~<9=9xc{kZT#We>-yx z-v(;g@4i?ic7C@q=l4d!@_6}QZrt|!?ZIk2y|V*gG6vaa?R@-NF|DwlyMAF@Uw#@} zQ0MF+&GmABW$k;vfp`B{W|yb-O%!JzS$Hm~g^za}Z_S|fi?y+N*ZaLIVMDo$vc_sV zkNa!6VxDb-PlJ8`N_Z1%6UVOKr_YVIVIR+o@6BQrtm3L^e=d6+@#{O!(H9pW?Je)EXwE_f%ahjCP{KM$@XB!0MleZD|&+Vg39 zwKw*Sam06dQFVP+>nz@vR!xH+nGl;*0)DXS3>%+Xy|OeDC*z{`Osf^HwD*$mI@az! zt2XvE1c5=kuk1j4_cJghmU`o8-%J&6# zhgor_&rf7|UY&7$U_cI-Z-^N6^Kk_)F19-_nmM(e_(mk0d@IkZfvjBZco!7yEu}wC z$lNA3mcqI0d&YvPz&&vI9Q?2~8zKJdcX(a8*=>E z`N55Q#k0&GoAVK3@&tCq_x3MsT)(hvk$)ZxoBHrv7mJt!7Eti_TtUMGhHq}~y(e!s zR&CI(^`tQq0E{WkU8IW32XN-c?9) zFBjqOn)A)y7Wi~TmIR<-`+$%Ko5Aziv;1cTu(_-JP4I;6>I+21X)!fISOIFmAm~Jm zl~36-W9exPh$`2GIN1Z!%K@y#R?7Q9ciUHRwmj<%K7mk~C^yqJh+n0+N4d1ce_ohq zRo4CX7&cM!C1{uW;SQ|!7!MFV@`h&+&M`Rla`dakLYSztbBRC`{w$ytnDSQ=Uhxo# z1Ah~b2vJEhW%c&6p12dHSU}Jcr?)odGcw^Lz5v_PaBo89&~FzZd5!)6a5~B0250JUp!$C;H zLi)Y{(3=Pu=PdX)6o4%$;0R1)y=cjmbvHb)w{YMLA0rm5juc>-c`0U0AmEKwCK|3f z*b@Me@YfL^?8_an_7m;*5{Y^bT)4-fZ77eE{r8Z4)BeHTWQcWa^3JC z;^oXxeaxWskv3wvOwpmRFSrj9<1+sM-Vpcens+sbCJELz5>ED{`n&)z2*rl;Rh~FB&5FLVXWhbBsMEVE5P*-yG z+qzPY2|6^n7C1rV1uSBf$b_#G31B~%CVTfhyy=38Y|XiW-HR0|vp@jx)vMotB^RWV zyZ{F>_5E&D!O%uR0LXPtE{yj|5X{XFrpayAw--PH1X@qO)cSm0KUDMq2)deazAV}J zfaJ-AWQfiaIuY>0)G^rvDiTr%xwUr84+eD+6(G39D~15(8llj#j-TJ<$=gbFUmOJ+ ziP(K5I`YGFU}Sk?@*NgYAfp)afM^rlxVnAUwE8a?+y!WaqOOdo0g=$)1MRGJ`zP2( zmJxujgZmf{BD-RcEGO?araXukE`B}_0EA%)C6&m?3L!3Vu>epEu>0w}#f1}fnK7R_ z-~%7;2?&X5=I2xW7W9<;uUIQ@L}*@I z51bOEfgcb)AzIK|R8`WUSsfgt$+STUpfC5kWAu14evW|KY#>Sq&K7}S41l+5*e`B& zc}o~}&hoPl%Ypkquoux9n}SMrVkugV6$_!E;dpWC63hC*T`H7*Lzd%(s)T4s&EK2| zr|6R1zFkBQKg6#KnsCPTh0HrIuw(*vxHpUmV}z!B_2RvxLUAGdz@QBE)aOiJwkHOLKhRms#r# z>cEfCnm4hkH<=7N2mL^#B0TY1(8CcZ0OF@jPS^b75>OdCeXb#djffA=W&_w5FP@#k zF}Iu>GK}%Qjn^${QCyS2FYPDzSMQB0;})i&vQDk*!l;lpBFH5AAh3=vO1=j`YTS5~ z#qfg2ZeclK-WqJ;z52ZaA&nKqxQT0K3*I{=^IxjL{19}xDPji(Q{ck5BcC2`_gPBKG>ArSu-nwxjQ zuJQf{KO|J4`CyWG2vHb6Lu2H)SVbm{$isis&Q27zJdm=2GC4a2801Ji#-I9uzjzbfC-PhRX|Nq1Puov z{bLHZOMrz3EQKjyKFj4$H-rVYLMe>tfTYl*58T7y_)tJ zuq8At9t*|5L?I}+6B0B+McNO`;wbQi7jNz0+I~-JV@t~6oBVreR&Zplj4Of%%mz9a z#Z1HQoBjvELY4;>_XD`u5rFu^h*wuP5fTc(^Ma2rcK}7aYMBaaArH|`;6XfPG(%_C zZ@wBpvJ@MTivjd`2i0G!TskY>@wOY|;kGU~0d${OH-Qj1lu610fmB6T`1DHO9hJgJ zxK~kiPxv(iC1pp@q zUZ}_xi5Tp34lEmt3SpoFab@VV{lF>%a<;t2l#u3ib{99`vp+Z)u|E5K#rNHTB`=^6 zQ!q~>`2co|2shTjMIz5|c9z$0gio^V0e7Dt5&eD4y=i%HHkdiFp8y;YjF3BU7MvV# zNMPjwxMAT0_bRx{^F9iW095f^@G3zr2@(guWF-;6o z6$}z-ih({xP~57@^`7S*rYftc1B`c|d*h=v0~*nF(B_+{8Zc%}3{c{YfG$k2EduOB zHDY6s;(`hzZf^cP(I>?m=YX_e+1qr>Z#`LAKKTGkDi=#f9LWUuxp3A}?^+t*_`y-Q zGmxIeEH5aMh`Ggl*2lg9$sjYdR)d*Aegx~|TP+0wP430yAQ;~;2ZAC(0P93-Kqb10 zZWNP(0oDBS<}(o}7P4g2E8_EMHL0Beg zhGlv^9J=0nz;N(XJ`ZYGywol;0ZRZhOo>1O5szOfPriZj2V~EQ3ylhI2t^&yj8@S5 z1_-!e*BHivTbU#Rb8eH3E|>6vwoErMZ%JJW>;mD#wqeO!_p>fT6iOQU2EH~u!fS;8 zz$v(`6VUyNCZns~eS13fs73IjXM<&3uAOhIAiOCD# z#bglK5x>SdD>NH+H#*h@ix^(NjDVLHmVA?z=B))db+isB=} zz+UU;iAy0)!!ySYdOISaNv@#RhGRJv6vOBm*^<)AIjO-(fD&{N7WU@PBZLl9jVHz% z81St?pk1Ch0vtKQ=22yYyF!>CjHGeY5j^IL*ugv$oN$G!^4XtBHo|Gat}@ZG%0Ezk zTl&$!j13F?>LT{nc!9d`TG&~^epliuTX|s%P`e$g8DTg#o~-7FvB*AiSC<0qxl){PDpxuS}qYOa!!ZE9kX5bm1Na z{SodVDg4M=C7^;j7c1Yv)zZ?i3zjt@YVT)i3|qWYRUNK;a{&PtOK2PehJcIig2q5! zDmH;b_JK7NtgfOnL97f^)@zv_dU7rV2`-)=UH~KO@Fb8105_@T`GLoasJt61?@|_A z5+jE><}wkE7tWCfw^PwZF9Ca~dNBoTu6sss%g_ndYy&prS%Auw%X$uVf%-#n`1@sg zl)!>2Oxa{Jv=QI)V-Gn!@%?^PVXaFVB3uO*1I3{jAP^rz+~bPiG~KNrkYiA4q>V>| znlK9@4~(4X;+t=zc7!;TyQZTGa(%;0SP57V?+eu$@NxM5r;=Yk)>EX(6Z$2W9w0m{ zW4O9}I+y_3sdkX}0o4&;`~~W_Ah#w(dyF$a9{MhL4Ac<3d=+_kG4KwOL(O}+p`$o( z5UyAp#0YL!>g91Ps0daOI8X%f#nH`-V8(YuX<|mn?CAJREwOK!54;D!$PWPoc7(9V z*TP{S^cu7FfZ0O~KcvD7p4PdcP1Tb>5D;LWSK5RFAG};SpymTLWFQ5J}y8R+5AqF;@1<1JbJl#0e$j;taMg<8B7#< zIHNf>9DyH%;)DN{zz;rDVMC_#VtxVzKZuRGBhjosvFeSmMNIqyE%@$5)FW(Y)Ozpm z)CjF=iW5^tHh4&{-kh7)XPlu`gmsA~RO?A#gZaGT;@-heq?@0En5iCGg?}o>m>#3Hy8e%1@`7=hOkJlg;0vB{ zPSy;EnZ+OST-;`u($y2dGWI@k=J9N=Ok^;?e*;Oi$~qOzd++jcYWP)Kq=Apti>qPL;ehI(1;?(lx%5q!)#?#s+grE-5==)G^tdT`V zgWkmhlsez!5jm1!y)d=maUvdrZY>x+P-FtZ;JZ;L(6Ih`lRNe$L4zr`e6opHG_fj1WWnm@{fzV;SwaFY1eB^Nd14z7zpjVC4*MlCzQ~6OD8YH$wE!kqW`a|~ zL5GKy!1W-^0_igw9pF$P7;5r1WSgNaVZ`vrRjTYD)rJ@;9_o8g?LqX=LQ(dZ#^`sF zaz)(L?95c8uxqWi4tR69Q%FaYbQn8VhQhMksVTK8MumrNqUAC+6)4Q&2YKg{Do6N9 z5fx!X&c~$Z%8*^w-f+a$&Xc&R61fI9$P&2K3F2{Dq{3noRt8M`0+_hB4zZ{=g!nl` z@HwX>!KfQN>TX8>BP=j&f>2lPKM5f;#YMDP=~wj#CJqmk9&A7rn_ySa%^{oGQJ%uh z*At)N%a1uEJZ9r*GkJmqBBp+Y@R(YJoxxix3pneRcAxU8CpC{{fNI#&>&Zf6eevVh zbew8nD%b{Id*wH`&q55-c_}~`a2K|7d@)IN`wxXZ=uALhiihxHcXFvK2x#a$RW7?g^CTzReF>9;^n5jX~T*eh+19zssqu z$?4s>l?^FINE3YdP>Vp4Co3JM>;Vdan1GP4nY0aS>Y5J1CCB?L#ABYR8HH~p;jlWx za)CcVboT!>$+HkL4psLSxq<3S3e2kh-WwE$vIUr9P&;e{XIX(XvaT^aqxTAC{*B{D-WhcfdKaK-ol zod4o90Lnk$WtU6L3)oin57=)Nc!P&}%7To8H{te)0NX%Y*`zHXJCRGIb^0fjb%-G z_59WRDB1)sswHp41+fnx$65im%QptSURf;62h4?&9FkTo{l9Sx7$#8?Mn*IRw-icY{zZs%{ML<_ucXStX#x6f&P*55fX7hqaTQo`7yrA>fy>}xB)zM zAyjg{9(8d6`TCTYP@`g5GZ?n2*^Q)%Jfe*_RQM(&_MJ828)&0IBv5LZi3Puaj`dzR zP%=xBv0lKBHR^Zfx{-OWMB1Wk2-f?kf{3N^?-zE3^s^j_+py>`y_b9y8~Wt>H}(zr zD`pD$sOB5txgMV{EU}mbnjTJI5WL5c{r099VJ;j84Q>h&bU!)P04}VhWDHg=Lx3Wx z0xk}kskBzygeTTaxs23;Y0R-NoOzu>A&5PgJ4{n^&lrQbBg81-Rvw|y>jV&PPul32 zPev#Ps3TE4m&?;6fRzulj03mUAz=;qE*=0w?BIpDA|Y@e83M3?mAx0x=Z?)F$MdRT7?a!$-EY zKkR;s$w8p!7)q8p>it`#R(0sd4n&Fc;YRBfzlzaI2x;7%?VVx^7DrXN!_}+bPL-1oy z0|q>n2`++!sSaEdq=FAr0x#jv1EEdGM)+B0*6KY@;VAI+7|#&vtV#FG4itNOp3vgW zyYZ$#Y71@?I0|ttgrjM85@w@MMC2{I3`^H}!BrTiCwDldu^$5swjjXXx8)ib+psKe zmJ>yY;YB!yik*z9jp*mHO>t%4?vT;4kyg!^&5j~8 zY+!h{aj-uKUh6uibztw7nm-X5$kk;t@yiCgLsiPf=bMX@&*t`QNQWI3j)IRoQROk7nM6lK7M-cyF5|qqtzGjkG&HU z(UcR7l%bH}*E#{Mv3!dq2D9JLq7Dr_dsv_bw0G$5qRT#_B^iAA)Q5d9gey*oHd^|+ zUr+dNC=+jMig$e$W)x4Lszf$gw+`Y(3xtRxIJ+ABREvQ@C5Ev_I~AHNTbaL_HOwcT ziX)o|;U&f}@X!zfu|6rjW}i#9{x209Vu+Cza9pgdmFS>*#y`zq=a$h39z4M;&sqBo zTeB4drDBE*P!9OV;U|0v^G1JP1qxAdW@}R6#2`B!y2W&>&2CofwW0+^)37HbZu4Aj zs>#jL^;|u~jPK=PJn|Sy8oQw~W?ev8x#8pOcQ-gB?>#{=7+KgJ8v- z0X|@58gAl~O#*=NHo@=~?H*<{)@3ImV&1Ta@tZMC6V2i2A&_TY!DH!p^9G)~*&B5K zsC{mpD`U`t^Sq9l2Uxj3cGTMMu1+Le4!J%aT+T@mF&b_ZZ}WQQ=?k!6l>&HXp*lp% zvng}?_X-x6%#^GS#6RrK@W|Vs49CgAO<=h#@dxPFIvGW_1pe0^10+!^TR)5ZN3qzG zkVh<5jtl^<{#c^f-U)#na2Io;GVE@bj+N7S^$Xy6lkJ*>Y8_j~`kWtii>fIp%Znvn zurrJ0Ey~`R0BWHMWV8z0`AXJx{fhm~(gRt<|J%YIbCG6C5gyi)*g4V5v;y%?+x)=v z(1iG7YZSoN?}_n)NZ9)6VjBpXg?&K&VT$=v?={brBTz z=5q@CG@+PoBV>jK+!ha+@&U}?;0-*H)4(ZEW|AP-T-v9K0Fj)oWU@Ac1CnADKW}28 zzx7TjC`MRjlN}i&G82P)vz8WcavxvA79Uh)xI9#1_1+_irri_RFZ8OZC}EHjDfk)= znn~K{RJh}`9{B;*yhfS@9lkz`W^Ml)HXd%JFldCGup)11c1IGt;HkEtVf@3ltn!eh zzGt`N1F2dUDs2zbaNDXBc4dKE2z`tNg>-OJHfzN%i)aPzdoo!;vI}^#=SJvy^A4K?P^= z@cYohE`!E^?7I1E0kE9@^`NCHv03ePofh%K<6*flY-nWf>CR@$CSZw(8KIzULWd9y zu>M9f;R3nd)AVY!FX0K7m4Z)?)kI)5;1fuDO53#|s+x^NselJ3jsIAR)92v^-P3x^ z{jq>$Vvvv@Tph-17a#w`PzJ{5N!;g=G+eVO{ZzZj0*txGttpw<-WFWI!lI>Bm3zcL zEHxe{V|5O@CcOLW1m^nL@MjuoSf&c)eflRq95)eThoq=nS995wMji~Y+RajOA^jfc zP>)+Yn-5Kwhw$==RR)DCzo0x)yb&}2(JM;ntdCW7D&4?WtI1IfuMHZRAXt6NQ%%0R z-B#d4Fr_k1@ppt-qadwwPs7UOU;iZ=2C4U)G#lXW8ciy(G56ZHqW#-MyDCkve44mn zp7%o?K@EwJHcA-hkP+*#dlGOkRcb-v{j0vgVeywIzl{$vI@q8*irre+BDV%lQg*+& z@WTO4>uDeK7-@>ra0Ns)`Q_)CC93f@#eD*?H-CAO zYC*c4B1pE+uFsr3rbSOIR&vl5JNN!y(TmV zdK(5;lC}$7+|}c3O!wNjVZfB3RwsUy&+ad%9#}U(8bJp|i4zn!!oxjV*#{0)%ff~S zPHV%puG>)xQ2=KjEL;nqj3+%`vdV$@XX9J58X63XGHX~G9=2du_FH0cR)Jq2jR%7b zAAarN@VMn6#Ci6|ZdtLC5dhORos(1Y3^am3fe^!rLB9eU{a*4?Pd4lnP6k^RTgBp! z=hs(_Cr-y%Oeq};;5puHyW3_F&;h290XEO|M)5$)CKBLmR8wu{K0>t*<~0sK6E}`& z4JA^CW4*RSnNRtwn1(4Al1psmzi}(j@fdfP; zjC>hi(X?G^<J6>U@otQfRd;l<9Zf#EkvC4Y0@$nHV6VyItmCA zv7xgm+_CC@$_5D01g@2&1Ia=|!adk^@5{&-?C3yOW($1M%r_J)T2T!HeQ!Vo_5d(h za4OWnXR=+5-HsK4rF=eKV9Bxu)`MFc7qMmweN0x~@3N;0F8Ayd53{rb;q!!qJAzm* zT(eh?x z>DkNWKc+oyMEDd_?Vlx@#A0)KFga~T@>Csy(C`T}_sJH*YE}JbOJI{qcGN!9s+z4W zUYLvrrYL)F&pfSBL-_=lL*hUY%!!*}xvyY(-1o)so_2Pi^|9;+%MRA;HOOSM=H!%| z2TRUT*{begfl{`%Nc!C_@V;7eaeJ0hh@OOakHGCw4956?1?~O*j)V{__98qhIUU5- zp;a3K8K)(t6XF{@#bj#pRLBd*fU8F_$k)uBzy)vtf$9TxD*^{APf+#dD1evXcTMhT zYBlUkVJXZ=yy{vuF*N=%wA|XQq$|ma)!MtM*i-o{$7b1cYv?sN?o<I8AiM|VFTn&=d`0PyMNxPA2;x?2ZV$e0JjZq={u*=0 zfHYfVp#^K$yFCHJ^k6hC4Q_o`KXv(@CtA%%w2@aU7^`KSIJkBQcFbGvI(x-x-Kx18&>0Z($3|oi%e##g&n+nx1cTk)*;n?o%f(i< zU{SKIrFJEsNOm|LK|jbWMvKXLA%vo4^`9rc0s6e{u}jts@zqOrmugoQJj|QF^Z#va z@-WkFDbP&yyek~E67>SIS_y6q^das0CQMvY{k|+FYTXeF$fi9{K}Rw3XvyN}HvpPE zG25O(oFZ*I+Cn~uTF%6Y7tmf{P<(2<5q1btXJRPCySch2D5BqZ%7*8W!9BH88Fw0VIuIDq1p7!4KmggiU*qu4Q;EMLac48V3O1hZS7 zX*`Ei?)$>R?uF^hC3n1)eyE7FCBry0fZY%XSdSUB+D|6}jtyW zDZWK)RqxkHT8&~gzxsX?dVoj^ zGzg2gO(sMQcmwN!Jm9Ctgq3^Sucf72kSWtuFXvR;<}KN!Q5EUiCQd7WEUT~x+Y0X| zp9<|b(+f7`!csmKR}arfCM$V;!}EOf8yqjj5o~hB&T!f4)FnGh?D-Df);Y0{LK%T2 zow+K$3|jNtSEwiJ@tT)+nt?rB#dLi)&KtulH-z`GN9$x;(7=4O(}POUDT?*J#I zD8hN*9m`bJ8*VF6GQ{G|F~-L^J?ymwy2Xht3m4eJUbVfvO#*n#S8veiecFwzRIy_k zm`4hC>>W#RUI1G1r%9gEZULFZDIO%kGhcVxj+^Ub(NJk@Ax;ZQS*;#j{BKs*;y4!1qI(4nlc*w|@hzzH`-fU~Z;@@oPs4b~?Kc{u@qi>)4ZWgWH=2R@5 z%0R=&eiQ(TOPxVcz{~SkyD_5kWOhCyfj+A|;Gx&^tQd%ByEi-S*sd25^PT}cuo1$! zv)*p@s)uuKJ~8iV->wJjcFx)9nDb>tv565uAC>`Y4)z;j85#hVNYvO8hVLtOTlAB} z#99lIR$Dh1)%_u}tT8Fhkg;d?ImP?aLqJw~t($wXEywM|XkR<2F=YufJIUugxD9KW zAra3h?HcyRpn1MCuPxX{wN$(bnkf7Wh*Y(+|XI}sF zNC_eWghN0^Am~Fz*Mji4Idcw{=d^0PPBaj7Gcmejs-UaOC z!m(b9QmYAFwl6QcKh)0{uXEdO{41KZxvMeVjkWa;!G?V$J7k(M`0|1^Xm0!YtcZHd z{AcI$fNyGnCK7|FcAyjo7sX)$^uv7~>k)QQ99+}fb~x;;S6fd!+a_#VG3tkL+ae}IN``-vZ89`v%>!N3RD5%%EOUbMvHkT++0*7^P-mQbAN+TsGovKx>*cVUrLG zJVP%nvF{dFbyNRbF{qW{V6hwp7H#$LVyB%jYg~IF}60NtRym*8RJUAU7<{ z;SN(nvBZL(-DOp}0A=FU8(-_}5(_z`CJYUwl<1J!0VMzIk>@zm|L!(SK7^tPk@ z9FE0qB_rBreAmAD7Eo`Vs#v&XlTjrK2In#wzB-$GYyk)>3*v9(x#fFOEVF42SsGVg z1lpz96?;Z)wOnWP4soTjn8}G4L-w>}FE@rZ?YU=PbMn+*1p89Ik|ppXA3t%7#CH;c ztn@aDA=-zMu%FUI1B4Eo9kam4KdRMuvy@-I0F)H`k=KbQZ4hT>-SbrXJgs>k&`te&zv*$EFtoqQ&eU z9@@n0$UJio!DCiXy<1#h@y)iH-HwPae~22q$P0+CHLx>Et%zZs*%uEa-f?QDX1RZz zTNu&jguJ%|+bo{bdCxyC!i4FJHnSbmvY^@!CLW%O`86iUpbI#uL`6@uP%i1W$NOQ?lF^PZKKATi#!MmqWM4HNm5dV6e}E&`l0Fg|N1 zxbv0HbQ#BNojkwgT-IQsVy#xpZJuFcp46Ygn(6$YdC1p99C2tl>#5eAZ9P|Bpf=&y zyo>AR%mEvutwvzK-!n3(H0L;3wzk7Ta9@Z9KH#Dt%cHiq3qSe15~ya9x|QOu@>(z94% z(}Gqk+sKohG+N9CL}U#2dKy0-`?^6C3l}?gn+&y|6>(TLt9sj03OE+)3ZNI}7uFf0 zW+C0@7?En-AqX-79XM-*-&sJ0L&Bcz&DaiclZ3L$9>?wD$`%4S%E9a^Ar@S>INyW2hrI*5 ztQ3(`UmgHWD`Uk>A|LeptRG6ONB5h}Fwl-RO`h z%etoqT?d*k9bLPEqih|A@hdo&6lVyN6-AECaG2f}P`M(LltdzAfD;MeW_bnN)iVZ9 z(wrHmz2frqb{Cw-ne9?xX;*88$KlV#e@lboku#|Eq$O?%t$l!vFc*!>i>KG?N$g@B_t zSqNUpjXOHep}7SSKSi7r*;1=ItB`wfm7Y90uCl@0KPL)LyJ;WCI$C7tz#Vu*wBiaD zT@=N)lLwcnC%gad)aC`JZRr%~Iky60)a2$Ev-cn=SSDqi8?t2h9gVt7d^URk{#1!T z-_5b1OG7;K0OUos7Fb>zhLcM_r|d%tG16+Uf`@UonmhNw<__cSw^e7Cbg)~CTWqL3 z9LN&dbSnVQQIXUVD&1;CAy^l+Z6{Y{F1W1;QFP>Yq2Ge@#ZJvyV2>T!_tlg7Z}1=I zfjUk)4Au83&KtKUck{ID7l4H&hOh%a$77n=yAlRg)I*7%J=i8n9E+!tdz^9G`82E8 z4WfkRlv>9am#qA%$)c`bNzU!{6Y_%NzMJXrm@RuK>vvuQJU@2gK}+nQ4!n_LXFtM< zX&=1xajrviB!-iz)V9gyk^V_?J-l@8pwnP2f50Ccuuxj?#(_2{INqPm)GF5T?dVht z4WgHdLu}z?mY)^14=S68R$>hO%zibg7(G6>xyknIeJyJ}6W-~lRXG`Nm``fQdBdk9 z^*vp?-Ev(NqHNoF86LbA`<{@FP*?l=U#m=E8P#WXoNmvPLsV##yEmuTZ;P9@YYrJ2 zk{n_cN&)r{U-*a^*T==6`$}_ENc5cB5$bARt0Zw9`&`q>03lVgp47hL@|4u%XIcUW;l`ElSj_f5BP~2Dx0_OdXuBS2VLD?FW`k)u zWdJ+kORpV#_xhaEWVw>V^B{wsqW9$EUx<+A*@IK8?ktn}On;v`7Q3`}aX=tQ<6pVj z>NId$?w}%_H&co_g2n_ns@b_{Cg7cvW>qfQ!IJgY3gOJH_2+h+ZaK$cXvN67KnhsM zQ{QE!a9V$cU^~#(V{ssn3*t^#>i6X6`=t>R%go<&H^KI12q|*XEMT(`2G(JBmALad zt7C(F-Sg=X&fE{gdQ8q5Gofj8h}Jrtd0T8^jww`owfF1skew@;*rVG%_p1Fd*Zx`_ zec#TvzrHggA{zIc4pWM?g~eIe7MV?_>d$=Y^O{1=842w9;UuR8F<3dxE?gD>-LQ$z zY1l>D<}|9j9%+1XF4q2yaYW7-6`Y)Gcg%av)&+$(+17j&<(>0g^Cmnk^W?!oic?X# ztV||o?8@q}5X}?alY*S*FyAT!BsI5@F8GJX_|ZwWsTb#{uO~T^)5iH=sfb5kD%mj4 z(xWYZ51+{vjnBNdh0@oV-aPF}!gxFn{7^_|7Dzp{!dByB@kDKBbvQGRAN@2&{4m;N z_9Z7tT&yg7YtcaL%2MDCfF?r7UPxP7^Enf$`fwz)5m?saVjD>7 z-23Tqm5iU7QIx`M0>Zw;b)OVrx539E8P)_CV%w(!`)b6w9RyiVbBpod^XVw{y~;wM zeK@jN$)JA3U!C4*gvStq{a;{Lch=Tz z77pV7-WrImf`1Xek;MH$_!Y1VRCw`iVAhGU^<-Jk#K|Qyu@eiW({Gz&=b21{Ra-;v z!Tq+KuwT)0{^k=L_8?S6n??ver-d^;$y`G?4Wr)n{W@-#$Z}@tnR5zfM&?U^jO|HB zT|@%*a$XO#aV`&TTK-PK2Gl@4@}2r8n5_6o4_k|Cch^E$59*)4C3cdvEaj^Fh` z7VX1kG5#|%9d_f~>uwW*3SzE{PjCp~R3Yc|<=kKx)pLpJhyZ-4w-dyogKeGMY2{&W z1g1C~cS)=@iF3g`aR(5qhkn}79W0BUz!oP`dD?Au8VS3%z3jK~oovc^&nN+|%=mCQ zPGWkn{RH+KTXBwrv+Ft^4rg{^B%I^s9&iN(e6>e0oG0U$Q`-D6t911TQqwF`@o^*D zs@a&%*z%=-EGmiST-2eyaAKkLo>rc+$u@M#*9=FCEd|G+$3-M_MsGM{n!|V6sKYu_ z8tr-W!7CO+sB)xV!PHi?(g-ZMOeI7(*aH_nRiLdY5ggtIDSMoPtRkvx6f7~ID9?%w z59rau7F-pP>?)3!xq|~VP6e0EN28qiU$6WXnjh;z^FdQ?+a>L5v!?DGoZ2tx3>j!m zy^;LC>~g{oQGhU1PVqdkF|PX1y=EfiEFP@c1F@c zLOdNhfKEK^)KG*~4z=qiliT4CjwQFEbw?W>j7hfay>(QZP1i2oQXGmyfl}NxK!8AT zcP&nV;6VdHf){spiWO~(yR>+5_d@X?r4%o2Kl(hcJn#A5^PTgp^*jH)tQA)7+1I{i z&z?Pd=APV{u+KNabENVY4B9`OZVxCfO|7wINjk9ws;Q2nS%`eW{kD@GS!n-NK(aqo zcK&!fFJU+bt}dp^HlH5nJ3l(vC;g&=Y)?gqDU_1onkZt9hW+QZV2q4iZVQ&uw;vxO z9GMRK6f?wa@KkokofzVAGZUs>X`yM!Ry%!q|5HzRGb8S9V$869Xt|K4J8}_vPm}t}8%>(Utw0=4g^1PiR0L3;q=Gj_zq(AexZ^=*QYF^$Y zvi7vnjQOwM)6HGE8+@w1tZv1F?Yv>mONPKEWboMpI&=Ru8_eEx)d`IT4!X6OwU8Qe|sLXL%WXN|6bwewQ}cY`!i9& zk^NV*Ry#9D?Z$RtDtjHB-zuk;9ed}y@Rvr}6uup0E>>$_l61d3v;m2235#t;9QT^$ zo4!n0l8a-vquPt%IY@tgISJ@`#Z^^8?rE`%2y@rn2m+=gpFYoxLbdhkU=K_|EscI3-)7u;D=hf9e4D7oGiM{; zr&P7~+-OdC!4rV-OK6P)ArN>+gXH7umu$uB`=<&O*V@uE2D+IE~j z74e0qGEvlDSrjLI@7RpT6SW?QP<@lu{Q(+$Ey#Nt4^RbI9+lf$QiYy~h(PG2-R9+? zHmNE2lya}BA6D5Iqf>rQSwE%IOIOU8d>TcXe);A=V`9I{8?nzIdFhUGf+glrmsHCn z%?HSHJQ(1BgvKti?~0=R~UYTnHVK4a>yA(}WG9(mE!NjcvE ztQCv-urcbOlnYm*dj#@F|BXU+wMpsQZt#A=Tzljs5Ag>kpjh=H|WIq&* zE6zW<+MV;hnC%LC(5urWz+{@kXL*32Qfj;e_wqF2iB%QHAuLLRGrnLxn^nu>ghn>^ zwT`HZg;<)yoB)8&=xB~Ui%@O85plQuE*Iw|EOYgFYs`JDND~~VO;LsZb6Zsp!p`3e@4t?mX!{~~2#pN(JGWEJ8dEQtOs&^>X%SUc6 zoh~E^aIQr@C48evhIT>(-e;9(f{y||+EgL}P0~tr;?h3b?a#SfzIfcQtz1~=pB__J za)DULM$Psuw4IlmDdI1e&KkWyjBTZ_mE2NBt`h$06+C)w-+HpA{PyNJih2;LJag8u zfDnY^(6hl`tws(SI?y*X&Q@pU`F|c&=CQN-$SX&3@SV=Zk?Kn0o|5mKZE%g=lUf!1 z#ie&l=c0%n`=_J3wyFd*eplI>0?gMU#m-_U{9yZcaO&jjtAoQ`(%o$eqS3e2{CzP$ zi8;qoam-alM5D*7GjiswgEnwMCK){Z~u`jPuzX~ZRg^HWuDs{@1zQg}0>WJld zW#D{3w>gSu)HhpjpvNjQDvBvi(Q>6XXepM+I@3+M>&uxOqfH|U-?h*&G0wJ-HA=(9 zD^ubCER2p)da1L2$=$EAHnclbykRxm*)^Yh*>w*^A4R@vV8nw&_I#Ncp;5~vonzq7LFgg&I_X~H#aCTawjexlN^)GGnONN zg?BR1XnnVEj|1m(oWr>xWxkiakF{%~U(s?})Ramd5J!E zIWQ9*x?aqsE89>;y+B^qFMdvPgnIO=xCpk)6kC;Fh?h$kN9HQAE>|(c+3w5<`njEf zPnVs^6pM!y!k_vro%m@AJQDQrJ^AJ}FDF}8BdBzf)owYd9gsYwliI8b!^2=V!=Od( zf1e-B!~Vr)MxHw%|F$X2zvC!sBXK|*iLMWcRh>rA?@;VwsXQY$3z{V9oOjaGAQl?1 z$~pdi{_MQkK|u9Q2QRd!{w75TaH8Nc(#-O=cI(XB+ec6{m*O`KY?Y55 z;VnTPj$6`GQ5G_Ha^M78IGI^;!X2Ow$1y#6BrFDpg3awLU8&71ts#ygvO%S-i@xtE=}pard%D5fx6=z+k&(iKb%cd&Pa3Bg5Z|KJsR zc>lYYiKB{h%}$O+((fkWJRXhkupgj}B|>ZC z>IxO&;_~qD;Pl|-baJug;uaJX%hha-&cH^rYE(v~oD7YNi9 z;^avEn-gs2LzXnwBsp zHy3kD8Fx!ZSGvDe3bl8G{k2XvnC0)HKkT-*u;O|!>W`ZLsw1bMqW%w^-)^*qI6(i< z_>KN6(!%^7IH;S8{T~<$b1qAJONR$TU=PgP|APO0I@dom{-r&?CI7_`SBTaB2L4;} zU*Hec{d@AiyY~;%|L6uGX(w~H-!>{p|2^s~oXjB>LQoqgS0|W_6I2Kc=CK0u@NsjP z^IC9oaC2J-a)1SRK^!0uH$V^o;^q~!wEPR9f+Nfo>}YQJoA99uPRIipKLBJQAOHYx z0Im4J5C3>9IRyE5EI7=~tSkVQ+}xG`FyJo~YA%q6;RCk+t8KrjEFP$M&4K(NKCl%B zfRBfl1ITL$#$Ma9qUH^NYe+T{tljg(h0dsZnRQNwM>VLrrKX_h6 zMM(ZZ*U826FZF9#I{$g~r+e%neOaz|5ZL_B=z@XWEiL}&!3Qt@DKfVKJ6c;l z%wK;?UVkr#{F^b(V+P>EXb&69hatcQ;Qk$ke|9gmFxT%X>hH}H{+-1C z7Y7LcPVzzuzlWuk8x(2}v2^)Y$NW7|{x`V4=>HLCPX9;szrz01mU4o6K4df-S2YjE z|F-&n1N@Ug8DjpBJ^tIe{|fojEq|HGADaAU*~1FqVZp)mj}^z?!s7Sb{{QgrZ{ha; zu!aZf|5Wln^6!7#^&fZrk38@{0{tQzo)l5lF`j35$N0(Bc{2$(7K;`vej~;Q-{eC@$7m2w)6r#B*sK}sgJb6lp z_nZ|wt^Cm=>PHIF60hJ3MxQ<7EeG6|&xeLTwbn0}>e}gQ>pD#>O^L7pNKH|nV3Snj zF{|SkM9B=fGfTz-h~4{Z7&H^od(5;;PclOD-969&CSPJdY)|LC#Vn@&?edaJdU z*TF(#ZnK))dHD|mzH#IUrkW7A=3>Fr86+uL&M0~ce6kWVt0$q7nJt|A* zBa)yhBsO3dW-x)?V}i$%W-nrrsf{A+FK%xAZ>}zDakqcE7nQWvo>o4b^C!@$p<-$!3&67=AjJJHMOa67?)a zkE_a%w27*#F?yzT=Ol?X270gWhZ&62h?OP ze9S;^U{gkn4pAe}Y$16SfQ@{=6xZJ1v-5#>EHCSvtA2*C%{Q2Y%I*MATOErkx)OE=ZSjOg#0vY0ANlOid+%(%NNu9MDo4UMY>a1 zh9}s|3DN9-JB54jU7GrYrwQq?j=BuKok}zRn<^ai*X0UiNRX#G>8z?a>eR@3h~!Ez z_smwFVuY($d?SB0W+x;`y)t6zK|6XRbW4J0eFSx6aOlO-&Uon+5 zGk^yABsvi}pbImEhT-r@4TY^ku7OD2Yz3h;xdVrSEr`4a#9TI82nmeEigrl|$m_qg z3n4M#3As#CyEXCOISIN=%Ztw&dTLqT6?ua!!BMR$5=jJSkQ~PD%I$bf7LjEGW>gJjCdD%uMpu1xWIeWoeC7aIz2NxG*U2b-ng^pt zVmH5HsD2Dz`PU;d)J8pS$mH~nnVFu#GIR2@(}E);NC!ZJpiGh`NIz&Inm|g$I;*^= z&PT?PqHeZHGcRS6xsG0zHrdjxyn9g-t#V(hFcqXAU!Ok?g%c%8V@MRl5T#B4Y2xU* zeZ|w0KCsyx<-KH>7z6<5Y3= zUTL%PF9uqSk<8V3pZg^=P0O)p%N0Z3gtVmQcx4xQ54)~2`n7DpEXmb@BTtI}{vy`3Q>eqR;9DdZ4r=yj% z&oWCcNIP`+B4n7+l>&VcwR&WH&^%uLj6fyT5R*Oj7=HN9lca_6T%Z{ENu>@QBpjKb zLsumCX*VDxx(k~k?DIG_ZeJlc(d7InVqM_Xrz6&>e1dA@dQbgGx ze>LsLKx_P!*H)$dro`w$kD^g%jLJqz>(gd>*#wfLO+(7Z!)&E=Jcu^;31gx0Ae(my zvpN#)RkU&sACJixNb4I83iC3s&J%3J0A{3=L!J z%)dce!oTTqDxxA&d3gR;WjHRlAlW{jyF+qlT_`if_yr3r5?I8%IPbtErKQelB)MW?I$WAy3&j6!Q{I3Q_&SUz;1y10AbMChDnb}cq>y#VPy_6ak*hg$O(eO(%~-=xp2c3jx71trP%}}*u39o@@w$mf z534KTW0{H$&G<@+0!ftM2R(Jk6ltBGnt=d$HfFpTUgFpZo1)J5k$47$Zh(C8)i?Ee zkfEQ1mPn*%fz6z`SN;P2a6p2XU}k!W@RJJV59VJfZFCG0(dJ$Y)6BOCtiGqBsm0&` zzekH_#qGmF_fhhvSbNDy(&GxGS9BqF9|oy@N2V;XvIC~%h4Y3&Vr7GX%%$V1FZNJL z#}KAJOj~MQt*x7`M>%dRay#(R-{IxRmCqFMw)gLOUuTK7N^=q!NR<%A_W-c9Ch0AK ziezy-HPXUjitGM&FWZkB@1Q=1{Ii3$AtKT8Y&=FTzX<4tj32u;?@vOzp0S0?kp^p9 zCg2rEUVr*&f?3kiz&v4~?!s<|H;os2_z^EH&nQ1cLa))5X*uM|#bY<=r+4Ec3jbHQ zQW^p zh-c_hD-F3!`9AkIM|G!ZK}^E@DpQzU;B@2>y`Sj7u-M(E$)%FNF*cgs554DUr{u3u zh|0!Ivp(K(3^WmwfGq~DLqs6QgBu!p38_o6Gn3}082;YRz*(%$6v2s@kQdUP80cCO z-UIS+7}5%ZL-gY5AKiratv>vCb!zzaqYg1T+lL&(E+UdOW@)cxNp=dKXTTp)W>dqS0v9k;VY402MBg|n})xmS&I}5 zwU-65w1GxP;_kOf+Rlr)O=Eo0&a`iK;X~+bRmX4-DuLjcE*%Dh9ylJFFGjsF;BC*c zmHfd2f48LMvskO|PUBKBp(1ZG(gwtd&1dO-cQ< z!s#NcJ)Q4a+K5`P`AOWd20MxCaCD{My?R_otXzWOO{VE*LfYtF!o(`EEhxaWf|Cqa zu281ar352ZY1}d1HtpyTo#_SQS_KC-vPRD|!lbMjH+L>&+}5ZNFqCqMv+EFLhpEaj z{N~A1{RW3A;o=hEK!a>@vH+@hy{ZK~!x#iGqJTwGksYJbQ3Yyt-nELvQ0wfikg@Qt(=2zK|S4!F+Up>Jx zM3Q%A?|P*?9Z6tN$oKst4jC|(nQfLE_(4(7HCdu=Radl9tfuk#U1Ije_UI=Rr4j55 z{n;F&FB*yT)4@SBN$+~6pO8{cB#_(jITn+}$w@;3-*ZjA$FP-=2E&L_cfO<35KYn2 z#O6U?;K0I+N(w`)VDX2|hIB?QcURt&;y;j!XwT5do~2n!2o2XSpbO+}lGQw%9?PzR z5g-2y^&R;}5Vt-_5>~DPK&p`Ub1%|T2pLYO)&eZb)xb>_5kGdbqr^VK4?n!Yye${W zmquX8mrG?zS}^csm`As9Xq`0r-=+EgWP^YL5h^yNv85l+CmX7i)i5hTBtO)(1vv-K ztEt0SFjlM44HLz+62zmRA-VzWh6&;t67V8)eT`XXaDon4wNUzFPJ@@K4Emn4zhQ)= z4rZbC#hkz1w4w2xKjjOO`x8x4=L)dRCvHH1zhtb+4DDqM~oQN}W3JFMdt9b4I zcB)o|(qMP(I)ZOUSoS`;@2E$#}A)m(W8GxRdWkx~3i(N1| z(x^xm!}(Y?N>*FD*XR6{c&C92D=oVCk|^^Nw$CM_w+rz^GQd(m!`}b{0!L%mzS8L^}HB8*5k%kfxTi zfVOB?LX&8Nzt01>_KJ5vl0yIRuCF2=34VD+a?6YWaOnLQoyvyrW>5g8c3pPX$*#`# zr*g8btu`qfVL6>}Y093B5C#EE8mz2Lxz>3Z`A!Sh4&BPup6!(d>ny7;2E3z;7D9cU z=GF6zQcyck;g-A=QtqN{qN}2cWHCwsySV4^2eQV~G%D|3m418QDt+8nNWIH`0i#5 zOFxgQ=58Jsh>^3+>hV&!|C&;&=F|q;o|%iZFiJt%PQRxKDGRkhbtZV}rinv;KpRNJ zhW@e#GvMPzdWVF33@cX(sWUc?c7rKf`UgrIhH!*#CKI8UzQp*a9qag5@a9d}gmc~V(ZMr6;fE6ulhP1wbR#}oK-`v)%PHsKtSIx@;i z2A^%@fy;99Svnq}R-bUDL_Z|RGgN8PPG-R$zDE59hb1wvU=bkC07;#4g+t`Y4w zZxqvuF`P*VHv`>Ow4PyPca*#k=YIhOC_-i!;#aB3o`-?csogsBwN^8Nnde9uz60%s z>z^DS(JY8p_!w1oGC#ByBflR5)~WLYn!Fh--|-GWHoFz95Lu0q^Y-`)+PzSaU+$dK z(t7i*z(u9#8F!0Y+|tOdt5;Oh?D#Hwp311woRf=h=~!zw9W6(HT@+|PG&m|jW?omR zN7D&oj@ZaZY-n!9C!)4>>Q@1`_Y|HvhhmE!M(nXV<`2iH+WP3&XMkajZpnAaUAmNQ zll}I;(7^mRhPk&h_m&prBAk2LhI-l_-#R1LB8<%+yi`LQ+MtM2V#~*Nw}n}W6kk)E z{6&KnIwVRNRJEz6LqRAyxE7R0>nCZ>0I%3eYqM+#yPi+mzDnv#jYQVPlm&;6NRkII zjF@}1nYPjNm-Ud4%&`qAC=qQiTA5`^PoV4S*?J8Y;?alnOoTedOQLoQTaj-Acb~do zG_%O_Mopqb5P#-%`x@8fBI*RI>Ef9YgoyU&hH8zxnNMM2Mz^`j)SGLrch2sk@=Lz! zuDQTK`$s+5-z`|(2j|odAA>X zpoV(XztP;<{PNl~!PJdqbuGCoOM9m99L`UzhF;`S$o>u4IZKszS4=e$5y_=UERGdK zu>Pempz!B`$k3R7v)=PPD}R1}psz)CvhfrYm>pk4maP*&hb=NU{)%O{xlM_c{1=+mRoFMZ z573ajVs_nU3+x^wRTXksy;-|6)wFqHf`!ZWbQItDt?(xS5m?2}s;g|v9;d=W0mXB@ zFp9c?e&vXP*+P1f*##AsY*VPs1y0VZJSl=~x|H@;@<&qJR9;(gj-Ss$oLT0m(ss@1 zK{*?dgy2z4a(?L<;;e8vq>UrcSIm&A;*aF((uld4krZM*qe5oZkj$ivfW*ApKs8@M zF|n^o?Z3p+E23Pxnq!N{Ga&>Mha7Hl@jY7k%u*k-PGC>7I=T}=^WOlKBJ3d_)fWi* zFf{7qY!@g(uY)7RzXvZ3B!{xuc=MI<(HD1YkY(s#*M>WD#!t#bF@DI2-xAb!#Bhs5 zps;fd(~Z0-CLFit!~?;Q9p6yN8$8V=&0Q-TgF7X49HAQxMaLv)<^?}8hkKJA_ZVij z-r)yqug(}+cq%4Xe(##h*o(K&8s%i4z~xhE@0^Ouk;Q=TT~TJ7f-2JXEQbcxC5GrcH6fowVpJHU&n5^HbhZ-2mI|HXMtqr! zEBX1xo{z~#07@7q!NXR06!~V6l}0}t zgH+2Uoc!?ZI9iLe(7~is4pknzo~jYa8To2#yReUp5Fx>*lKp0dn}dqjIJa;tMqRc)#zb3Iy==;4!}wMb6~X8O4NxAxh_;slrD`(At&*~bQvp+}fc z$R|H(sd?QApWnse8G=mh(ydi|&oR zWH-_D4YmoZ>;y6cN&sE8{_vKBJRb9?n+v&Lj9+m?Od*ve(GJl&BSz;zkg#}3D42pr zg&`xN-)%mz!6&yvgNWZMUNKRaP5{XPCcH)nwAMEi8R^}dQ*ekGPMuPaBP+xNHP*$J zFcEo1A0V^mv<)tIGRoM#e;aIbD6~f_u|T$N5&R7k#M`J`P68Ov#3?@9*>+raBZ2T+ zy}#rL-h=u3i#rv|P32lb+F}pvjNvCF*T*`EJXjG;K$Kcq`O#)Vz%Ixt58XE>Kj zwFLKEI+lzH_$uHRb~m6m35UZZ-jYQlVKVbAxMGhGGSmwMoQKgk>!APk(hs09O+a_va z2hsB%95`iiS;W%d$lXqtbD+`^zsEVQGezfZkQ6_T9B_$TgM`p3@CZm_GYt)vN-x=f z$fiPcwx9Jo48#fd6*2Q{2|3KBde{9>vE@U9_|`xpwURbaqSqzyMYa`o2$kC@AWbhm5tH*I`mo4AI@8KT&@GjV_LS*P> z5whhy^s|KfiFInwKyZOZe_i;*8gh))wrOsC4`cJ}7iVS4k}nWR9rP}nK$A*DKluW= z&pVsOPe7lv?mNztY(XBj@r67JEJ5VcH^$bfA(x)Ee&Sgwg2EIN_OtSn$qM|E180fE z-CL4)QRsIU7GWD+0tlFR+(ThhpAV^hREc?1Yj_;%CTzb3s?{eY6}^k%^) zs{OjQ^6uv)?QP|^o;RvlF1Bq7tq%Q(!5@e>766nn63=nEkb^CO*)D(=fKdurjW5*E zf>~C22?>^wU|V#UiX$zi-9R)A2ao0pf}P11Wo&8*DS{z0=)0QK{VK8Cp}PW3E-+YJ zX1jDMIkuoK=W9k4mnu;(u*=RhLNaQ122{HsMLwY6k2?mTJLcY#&35;QHF{QO_pr7SxkmGm z?|1_3o+h$eXGw!{)Oc;;ty#+RI-93TT@6&KLXCOF=`hJ6=`rpEYW@hNCYYw9(MY-DSP@(Lz8VrFh(7|KsSZ(nbVoW!l!4zstqD`X}h@RhZ zqntmCkHydG^~SIzp_fXOX6O;KRpvA0{CMDKHM1m!bFrYjW+FW}*`RkS9E3wAm#UCR zP{PtpUo?)^ZL0lb-iCS3Pgv zK77`*M0!%8-PdME{H%{QR(s}iUe*aSjfLefJKrO1vQEW=gtAzk_mlMNc6aA1Gq{&Y z>$Pb!^xren9lN_PGWQflnPby?Q6L(4MJ&|~KVt~bO7QhF0a;%tqD#K$2XeB17)jq^ zN#K-Mk+j9`Iyw@LV~#@)L}kNzW^*a(T%i$b)le;2$}eYamFf)41B7A+RU@$g_uQkR zwm3eW4Arq^9oi_BWNq?$Q8SUI+Lnom$P(MFle$nh2S_V=qMNvYlEuNk}XJqDKx@3 zyG3z|ro0~5V(0n23ljmbfcZb$lP97K8BJ$2$GLt|H~M`TnR&tIG8p$W;Y~N*Ea)sOiv%RW;%mB;^~Zd6 zaodlmUtj!6i!$(!_q_v)y=?tRVDvlzGLkLv#n?j8!@&ZSUi?$V>+I%H0y`yZ5X|bSE_v!UC)5j)ZBWpe zg0(9QzoW@KMWL9WZDipcvGlt@~(Y z7DaPga-w(iEpM2I4htolDMwnA60EWeG7vVPO;ZrPs>}YtseZblG-e17M8EV!KW2=2 z;vK+Dm}0F(#pl^sDBAGp!s5!jRI_M6{4ayoI3Xj(8OwY${l#sn8pe{UB4_2mB0#3=qMDce;(JGvFHeK!&8 zofQ2!euEr`2_Nm21uaQ~rQM1l(#DRZEa+oBVA3*Pd0Yr(4e-tWxRXh=Un;EP@W?NQ1T-Da_C3^|K<*>T=ksM8(^bkcIb*veuiGeji5yexq zEMtA@9{NHLr@uc7!1ENV#zxYS!nUoGn2b-~9FLgDSutHXUKOp+?ea>=WE;ly8UY1? zvq+-zlZ7J&FjcHHgH`qQjm$p?bVG%ENO9*Jpg*R`v)!M#P_dwZf%J_4mEz#;tcR5h zXy8p?lryZj0^gRw>)@*BTDoEdNfkvNcE+h-aCE6rXc7ejUyfFT zpQk9JQg4*w{#+x!@qOS#U*Dzu48|+DLsoOH24D_rh@nAzNKf_0Uq^8mO0emwpDJ9@BPGX54o1#8k~&_>8on36j-)P~1(6I*8x?B3SrQyI3+_f=$} zH$6*sJa5Wi!&~%N1RIeKQk($&eDPDAh%6%%B3vwnT`lfm6}#NU^A?TJ3aMvySyT}P z-bO!$nd3Xg5SbrG@>tW0=<<#GP?5eJfPCfM)Q_XBmn)1zX0j1h#r4t!GkId1ejWrmybk$!fFP^8HttRBvmt=mzHFK;a=$wZ3UPzR_HR zA`&==l6j{FAQm^k5o9yi3Fu1ak^PX0JWFdCuHK`yRFqP=zu4l}=!%J!B2t+gj?y3$ ztK)^9Ot&)gIL^1d2(CVQ8B%jIv>hqS12EAOP|z*bS_fI%eH_61)yExl7Y~T+pA0YT zSKuQex6SDu_K7O$;ms1_QzEkdNwG{bNauIBV|~H4azeB{R>qCZn|#2XX+^P5mCt6f zW^YYdL~Go7f;me{fq{VZ&W@@qH+yF{FVp(91~IKo44nvjSw2;p(KCE)>aa{hzOtR- z;RyBj4r?utVq6QZPtm?9mhpU{%HfHPgM;4UCVF&xz%W+gv!#|(us_B<2U%kcyWB#8 zq#k}L!nzr+rOGF(Yij=#&;jnn>aPi}-^Zr%zXFrIRUz3Pb2(jvI65vdMD~8CWg6qf z%XDuYGOF#~UqgkpP7RNZ91nYw=x5oDJ9sE)_G3j+O^4S+$7EkC(?*R~2|qjRvU75V zd8dVqWm{34&XdIeWXD|@w91`{kz;AqO2!7gt`6k9Zz&=VTq52NZZN2#_Gn|NcLfm1 z>|9oF4)D=+I@l$CxKwyKI)sA@IVpHS!s;XM7qqN>xJN6YI3kRAdblds5ZvSM??|aZ^9|Vdf z;mPryuDokFH;I}jFo;vtq2HEi6+v|eD$p35)Wppq_rEC_b2hgNt~;Dy;!3o}pMb{| zX!P!@%3Jgb`|Plrac@m9QFd>bx1Fu?k?2z8tpRNsUd+yVEw)p+?A#E@ z_GmVa4mmj-jTeQzpA)en*P{FkO%S^{E-f1!Q@81vfmwrEz3}CVgxxnKiVJ)zoTL|< zP6>8PVbM(AhWXV98417)x})VapP{Ki-Pb;6Q6@C}qJ$;A^q2~foMY|HYGRm2uY2l0 zd3gFF-VBps(tWtZG$)pI#V`unxA)`63={LrHM}qUb)RP05uRT|KA5DnGMIoPo7@+H zU8DJgPtw3><(Nw}%_JTB*TDstxwo6NlzoA;cM3nKGo4li@X`Jfvz!}~64a_}=NF@e zg(=5kkq}R=w4~i+oHUd+T40o`kv!oj01_roV!$v&^G#0iS#5oG(SbPeIc$wPxhnT) zs`E;<&Vh3^dhnA*msrRI%%XU&;jFnzCj^&qa`Al209#t7g8jSvZ*HE!ulJsLuJm}G zg_@6YjaAjR4j^=w!*&%IA*%5fWx0lkdXI`D|NAcMj12E^9q)h%o8T1Aa-BG@5o=3N zO7!$>3_TO$jf5~N#!*SfrIo(^@FVHf_nd0RudTq4825s1x!jgv-UHt^2i|9oN7);j zU7S<;xGIl=sC;p$e1C8&7hf(YH8y#8#HFNY=@D7$*s_m|9k-QQSIL^z#CK05EAYq& zt-hpmYs6@w9t%skpM1CW@J$=KDO6oOVYvd8v*;C@+pW(1B~hk6*CAM1*TP_rr3Sio zi;sN~NyIS#y`sqN2P@UZyZgIFrt2dVflLBfnxLcfBd9!iF_92^7)vXsab7r&RO9YZ z#DRbEx3^D1(XNGEoCCtB^jF!&R;;$6{oT*zKzu7;zuE3*RjvNgStW~2-nsOYN4Hd- z$@8%mmXM74dYq**f;i*Xh{4%V(xbRS0RCI5<- zWtCnU3O(f>Juc*fkXI6ip;pkQi-VAcS%Ng}D2RttY#^ z$*g%Fal#eXDE!$TE9Q_2jeKH~$A`DA<=#!4$(3AR-h;0R^TG02o6-e%G#t!9EbJ#r zCs~4x9jzn26(Xk#W%EzWMCDoooK& zo%_vW#Vj%U16z04%02anRgB@enb(pK*X!q6h2)mV*~T#|SGQc;ii8vy$47lRYxUn= zmT`?hcLGj^r-_%Av3XU}Xdjw?#h){Hc~^~n zHCkyR&a-yf=xAy=*q7_cWj*U9U)j^ShU%Pr_|5-D+ql&|S5?KuX$5iIGgZb>-|Y1+ zXj?p4O0#464u|XW9V%iVnf4j7L{bT*ch=@UJ6hPcy0;=eR61KE7>ICs-f4=sOhlq# z5FS|tko0YCE6$$di%j+>jzxG|t2)eSQl9f?R8*3J(nR1&g1YAQH%lN!MT z#TMXDfcgg6f^DW-Zch(+!CkOb>1fB+XSWlamth#ZF>joVznN~U!$67;S>&LHM6g76 z_w!*kM_{oWp8Q!KezsLyNv;nUm(MkdmGuv1ll5!vvDUioNu#%h9=Ljjf+J~d(6f9A+N~IMH=ETjm8k=h6jgYcYE!!C4|tu1qb0u*-uLa z%y}Y7N7-qW@1#5LKx+bi_l6XqVOfTH+lxOuMbFl-jGC)P7tCUI&##PHe?4mEyog!3 z#-4h$?#W@boeJMQdo6m>ys{zJer_&&-6@w5eSR@rc}IhrrEhAE;PgK{6@cC{8d$Rr zt6iv>+>&gkrZ)RHW*8R)@h65dh4aL)xTsd@_@9v9k}fQi=vO9Lljv>%$zKRXwdNkbINy=BHbs`*l4rx~>M>Q9 zgrncopy9RrBz!W`eih{5_wXpuEm}}H9wCzjIGj&Vaf^t*@02KwQt#cAEevtTUoV>k zeA-+!+4B(<%u~1p#G5@n?D0&F$uKe6ND*-4oqM}^6(ay5w})DJCXBQiF3vZ})e43Y ztDNER82juwU?QAB6g8l^)lFTe!|IZXnsLMRpNs4zYHkrKF}OypgGEZu`j3N;n#Lw4 zk%Bt$VcGo7MyrA-LrNmXK1aj`0ZOe)rhw2sN--J#rXi}^`8m&{84vizJG9F!wmH4@ zmp%TM5pioxm|EzC$)C>?P)NLYeGokdhj((%;D`kg&ewjpSSJrRkZogH2fp#$-BO~z zI^{^CF@5@Ks$Z`4>^dE9SAJG0=Zgc9EzP3{Z{n%q48)i@T7{0ZO5vcQ#Fo{1gSNJ| z;vb&Dsm9@cP(hj zdZYwZxjSq`w-fF^UA`P^HQp3RVP;_n{46X@Kh%7@K937KmGVc-U7aIlEE4gH$QJa3 zsg8viY^FUAkM&W5($iL!S6Ah0#WL3Onp)rTeB*@w*pd4xGAZoyu6O0qNwj~yR!KRL z)WL_uOGSwH^^Xz#M4+|r9%cp~@c9eXO0L7+Q^UoTr6S|XTJO3cd+$`MlAawc-e}3V zO8;#^e)llpC0(#mB3r1uuO@!Akv)`6=Xwr?w0g5{YYHPjE8~p|r)|Z@}Egxz3EdyA+bDxol z-d+pj>>WtoZyZv;lP})T50wfAu;`ACo!rUI!nbEd92Zw)SOd0MRk^%`@4hn_JWZSN z47=3He%`o$vQaQ34E4*=>6S2%DxF<5r;9j z(mGp_Q8-Rw5_K!&c5r-M_)+T%?=WnvJoz$%=A6aYh3vDp=~O+wchqskgB8VF_~G5w zHFFYxoXH}>3bD+{7zJu=Q8d~}pLX%{O~{Z<3e9#oofJB6#67wZ;%Ym7@vvJvzj%R@ zbQrGYa|8L&mj7~URmsFRAv@bQ$Lof@L}wQMb6t3Qc5KZH=aYxP<)m(VZ~JvNS5>Wd z8bDcsb>E?_dwC82`E}T$*jHK=Ns8?FLY5^RaUxltlTD|i-ctdadfoYXvDK01PB3e` zC#5I>STU(mpJulfj|o~GJGy>S6lmnm9enO~O|My6p&Qi%W>%E+pQKBo6J;ttE4?^o zLj>)4=aZwPnS3vDrpP*|A%c8S&Mwe6I=@0Ae`}d8Sz!O=gJ;-*=NXc>+~M)t=ReP2 zW!f7G(R38#^(g%Ni-j_fwxUc5 z$y}R#w%FA~+(UI>sZO5{RfIJV)o5Fn{b6Dq8vbAAYfq+kMFkFio^;rzW#~NY)Z%cp zR%3)%LKBR~ z`rWkq60MwH91A#BH@~DF!jj}hRxjrG=?J%mHI*7ooa8=vr)efWQ zN6-DDp!>Lz^Q&qGwV&6bDZA4HzN)#ROTI#3ZxM?RgYHu;g(F497veM`$U@E21wR6M zaU|vmB%7~qo{D{!r~)hDyPZFE{0UE6q7;_^!>?p9!0YNpa} zWo)~b97GswZYbpEzFjR@f{F5pOwDB`y@<#_MtEJLKZw)%=ywK z+fb;LCWm!D@x$%LyvW^<$sG#Ln5ur}7$OY+;PW}98jjk$Ud7Q>T5b_{fpgW03>%Sr zK;I`{X$i2I96SH<=i5#~N29jD;*UQm&(*c%lh752BJ%OuuY=CzKjY_$H1)lAI+}zD zc-Ga99Ng{1!CU6~+zIa1vl}nqpCV{K*pQ{%QFom>$ZkDiiXO0Z`&cCWky&%tS|k1l ziOPDNb{F&Q?%Q+(Wnf;7_oBtY#+&?iG()ITu>QR$yE1+f`N{a(R$ul$M4_)D>!7fx zkR%b2tv)_m0<#@?B>TDyWrM)GUAb}RX2B`0}2=+yuhS(e^wc6}- zH|1~T-knC*NB2}@_BoG<7dXa6199#T&ut})3JwQEQ$L<^i=PUnj`{x73tJ>^{(~hZ zS%wj#QfA?=E6Pt{Qo{=W9{|EYJ-?69aKdn+(x_+9b;##b!YD$Kb?!Yr5-%|?|*imIMm4JW#0MlKHq-zI_;bK76o+?Rt#OF(cO_jU?)o0#PI|2~%>K!0PM* zQDhM$FtjGj*Je;8m8o(SNr;gIiK%>r>DmlY5RuJhkt7|{cIjIdAAkOU>5_q=7eG?* z0|7yZ$Qe^q^EuKuKoA6UDMm~Jq9h`WBI5Lm4QFEEJEy^**Zy&u{(rEzh$IRUqzSSl zuyX4cxpL>b|JEJ{`Na2m^>&#TpQ(`$@q2&#fT0yJQ_a%qS!gnJEsKQn>C;NBj_HI< zJR4P#DHO5));vQE!M3Jm*wc8-Q*wK9!*iNAU4GtQc0WKltq zatN{lg2Lg+fZ2M5!N{jK^k{aDXf}J?xVFxR4?5&>IWkIxXKp>uRK7q?%kt#tDNoLN zd~pAeLS>Pi{U!$o9sDR|;`p?N{d1LLd~Qfe(-_sKb`NQf$0P)3S_aQaN#YdKvMFTq zh{UWcZej;ECxaG?`Dq5eMK+Tsjv_=t1_9(`u@T~nWQ=$yn*mm*V zF2!t?(@vWp>T`9a&V!v}vU-MkeGb>P`SH6SaQE>M#r!O`V{vdcLQxV1gP6-3GpO1; zmgQmO>KK_U*{s6W-Y&&l4x>;Yis1DxUEz=Z;w|QCHO6C?7w+8PZ{PkKKD_&wmtT6B zz*VWwRJna^iDu8G*=lioVj?OUoqpgL7xZ}%}r?H)y=Ms2!C5D2U-Z?HUDl8}`Vp66{S#r4?ksEL_?2}bRf>+Bz8%X0g2tur?LErIq5z5 z!a0E?isynzrI7Nf9SBdFoiQ2l1eZfjtC-fLkA&^ z@uQHg**)L6c^(sY!mBU-9GlA(7HV^-dX`6fPwDhK95;{H+Ihm+sLk5^CA$4%Bqhu2 z%ryC2ncjH9-fWE2Et=+p3ymuj zb9piug{&@7F>+XbLbrd!RHaN_&2Zd0p;BnDxH!XP)MeyB*XmQuPm=}?ilpMlE@I-~ zJH9{YwSNE_!udCVj3B^o|A$cUM#1k0NuaBekDsKRG#x_Up;jxAL=IZU;P|vdEQlx~ zC_;cAMvPpWqM|WBQ^)rrAfi^7W^J)dg2vOMQ)E$LWQ{;D5M`B&R^;jFLmHK7Zd|(p zsmIgfHr;L?+p;MYRpu%cOmE1}-VRc#VC1GbY&W^k$a2~Xn4hgs(BZjT8`NjAd~|o8 z6b*X&=<`~oQsZV9;4Eji- zLbjNt*Bx@y>XXYF7)plIPKUl_(>KQq`yEnWWa18xOGBCSz zM;*j8L6iiBri<;lq+!a$^f+$rqZ@hV=cbV*nVga3!-sc~lng2fnyTZ+Hj^Mgm3_vO z-iqou|Fh$TU)%;Fp(+tS|M&jc>(MYTcFh(;n)uS;RsQH;c$#;o7Br0 z?p&$!#+Pq%*laNxn~aq%x{B)sNJxw)7S*X)+`v6IX9PCR#6cAW zs+AgX1W{!1X!}!w#N~}Iy~u@)RSph^^oDJ2UAjTRNN^pMYZvATV~63;#r9p+R%V%) z9)v0{Kexi{Y=L**eV3($8tY5bbo(xGB=Mu4{ghTO<+R&G5d@ruKC2%ZWy1B}HS?9{eDxYodGVvVZR6-RM+MZ7w1xT_^V|I?AZLv6&MN@S;!x8mr zp5bszDrUK|y2AC1OI$#kS><)B}Z0CXmv;IpY4H=(Ca%W zN`>WxGL>qcUU$gCe2ug2hl&u2-6%Riz2n@S#ny2)_BZl zXpsm*whm2#FreKJxw5`Ou5^j_KK+PGi?e*|wL6r{IX?U3GuD>Yuw9p}CkK?WIc{zm zJUQwyQ!djQ^m*y_1vZ!GxqAIJKYH^=vSsWzO0GrCgp;xj;r& zaNK}=R;ATDLX#DAIg1(CC#q=EBv}?I&&=?`&;P@JnN`GT+cB}c zl%ta#x+Ee<0oRu+Ol+50IZwH$)9MZQ-~PjoIPQ#aqXgS==nvZ5xw61PtA{|qr3+Um z=XKuu_*3>zPl?l*FtHh$BQm;5zuU!&0v76ZZmi|FcKHG%0UIkTWHgaRqee!R*?M%! zts9p(Yz~;I7MQNg(KS<|NTEDs1dKTXqkgt~b=?7c*5o|6Lal(M*=_0dLgJyTa{U>|mv$M=jRR}y4 z+c8O!konpinyTRW0T7@{201m$#OWd=9-$DRi$0_glYYB6==T0s5RepE6a{?q)vFB6F5mx!=lScm?y^uR62&r-tZ;dA zm10KYTVMJT8ygFJeD^N*A3R`VWtLa2H>gh6v8<4V**eY6DH|)RoSYnT`HKw_L?;aO7t|CYxvZS!FR6>b9&C3JQynh~7`YSVM8OYSV##IytjXTNfFOv_6rCvYIc*>F#!HJV zR2Mip{s7Mzarxq9s^t>DM*xKJ^JaF-R*xG%< z*pJBQ89Wy}|9l-?t4{Rt!~W825`1?p9eZ+~@#FTb?T&5czWb2)-g;r8VP-v9UkmMd{_ zWr3q+i!VQSfz6dQnymrV=|vP#=IxI@;r7*Q{FC2!osErEPMZNA-T$1?@e_Xa`@g`% z379eJTuna!03ZNKL_t(UzWLgVH0E-YD`j>L_xR(#_yOx{chIsGVm~IAEpYGQ0f#3K zaomuMtRu-VQzxV?D;Ny?yzGVRs_hTRrgMx-{gg5y}kiNenQE=uC_ z?bpA?;?g|BzKfB`AjulNu?bPgS#LlhYp8;N3{^LL!O0 zc&-JVF}M zYPYG@D!lW-7JvL#hrIW=Nv)7!_xOlPzDlR7P)}mk5G@ufM!bDyTes z+~KG1eS+`%L}HHiU_xWIf#Ijz-#(_0^?31x>ntoZh|_?_+YgwXS)du%42KTm(SUMc zfz#vr4ErZkXBYXv>{HA=Lq4mbsWR2sJesPY>mr_&VA(N0dFxYpy&*vu(i!ie<>o1u zrg^acIYFAGs1@*n06k-%h%s>((`_FR$0CT3L=K8763aQBo<78-%B{4FWBX(>8ka6y zN66|NpLV!(VVPh3#n%~GKEL@Y$# zG{%Dws-lxuQrcF8lFQ(`A%3hO>jrXBeO~CMzb^~f6kSc9**&uu3@v7-6#_fY$Q*Of zvbZe<{F{IOF1yElNJ9p;OB@Bbk-%Bk<;I0g>eFy^Hsau9NErDDX~^@JW@+_A{`{v; zc|$GH?wT0+fcBFv7v~ol_I=*|_)qw)|LpHkspW7y4c`sfeSF6C^%pofeoXWDDY}*; zt81u|h$Kr?SC=_H*kjZd$>nD_JU(G;s?=v|6iPahc+L<$-fGbwbZOKJy!rlLqh|`F zQJ!+P$Z>lcF9KcGal?d>Hv&`=LVRy>4#LJVpKN{1&Gl<^dI#9nK2aP|nO)+;yC1Q= zcb;nzB^g~?;{CgiczSfg>tDXkgX05i$6@5zREjlxFQis2VPpl)IwSgh2R{hV+ z(X@sLqJlUaV5mhNJ>5pvR8IRQXJZdBwa8`)#EH+uwaKUwg-n4|@Mw*iG@C_cXQq%9 znMiOLb~*@X2%^YLX&S^nr(+LEmXXpF)I712e?~+?ZZy7&;|Z^HM=7e1ph>W}P^2*M z`Tln{`J12J<=w3lgd~JCLP{gP_u8{aO3Kb&m#NYemp10Pb3NsqPj>k9{#~xDU#0I@ zyl{JyuRed3y|%^9{u#S_Ptk>lH(tBLo1biRY4ZjIDS!G0zk^U{@bL#a8C|5`JVH$) z@|hCZVhPLakTtTjn)_V6{34+jQK>94Ri0+hPtg@XQ0TTp%u$c2at%KaINskQ2qFYQ zo8u zOMG%~kHgNGoRQ`A=bs@JA|5^6BT6Iw_N{lBDi`S611d(5G_`s5+A59tIi6$TmHA%;x$v#DjbEoioN!LRA9GNl`MzS0oXnfBh$q*CIFg zM_#HBhY5weN~hD|&dnv>`tXQ9|KNa(CQ{a9{?R}9Cg1z{XKBpMa&cvj8<%ETtmkR> z1{j$faT0Us!aBeH#tjO(!Op=0_M3hF^e_LGj~+fC4WUwB<^AnPctMCR$!z2%91q%9 zlO|dkor|DTP^FZTp|iNKiJG0m^IU{@Li6a9&5fJXXYxdmMXlDrG!q^?+NM;RV$gB; z__L2VJKV#uT}IY`j?+Sj1fn41$g(JCi#UOgEXin!PPJ4*SMtox%wf3>X)Iv-V=mUq zOlcvq5aWg}OG~dIi4yPK-62X-JlAK~>+lbL<-5Fi<1%5A^c!K9zSboZ2?32legnmdV;}f_R zzSk4eLHv8t&;9am-R=yd|MawFGwQo^yA#ATV(hu}hc1_v=2@SY*;rZO>V-vC8X59g z2|*F)nEIYYN*Ij?sH%!6rXb|b**ig`WEebm>s4-E*r3^N;W#db zCws&~$c(NrQx4F@l=zF**ZRfRc(V6^{gWZOl4W*!mZ$A5_wU{1ux-=t9P*VHZt`n? z=XpN7cgmehmw34Mh}~w7$c>3&l|nhs!O1DHC?Le=>_xlZ$MXdYU8FWu;>!9a51#Iz zNHV_bF*TLpq50e49Ah~{{4mCg0{kc-31a4F zDh$jK)%+A|SfF|nfweqf>~3R$&Esk{b~faCi}qJboeoVHuIuE%I%vU|Km z947d-&)js1j73(6alod@S})~uA(X`!%2YWkI%zYl0<9J z!*N`CgEoGWGMG#lbRHvudJ3c~`a-@QaENO&^%Up4qOY6%V zoo(Yg7N&3Go}6t--}&`_{*6}4f7Q1F;=tp0GA0QkvKa$Sj#*xq#u`uPTPDT4L1QjY ze-dMj2K@BBE{$mgA&GhK<9qzyfBGS&7jtj7%W!BCh%sRpArccL0!k9|%;g3rXDyu2 zM+j{kvBc`^G~>=Ge&FGS9-YC6ypbU_&rtkR6t7J(*(C}s#6)0eV~r$9AQm}0JtmyA z(F}v;(L++%APNJL)WVNJ7NBA*vbcPQgZ5pV(HV(2O%PhRfkP0DNfH^?9WpU(qV(Ku zCKFROLW;V86WZ93%fv|-SprF_<9h*4n3DPywW%_SA`u4(o-5EDTPT7|82ZFRd03o6?OI|#CZtZ10_fFwyV^c-0=kLB77%r1WHVow}=+x$Q_C*tie zfH;W|B$bMm1tr7}ZH!EXFp(KpCbirYnkEtiFkO&%^Mf;Txe~wqul|TmcYr5pxRVhH zBDqWsB_|=B8_!810Xgv?5s}DX+*SVC5GYK078XUNW@k8vUqKZ)>@gh?0z8j7s5xbi%r6r!tH z?BoNTqX0qB2?Coq&=`#qM(z+b)d?bt zp&2lCU1rKt6!V4i%NtjuyMI7Q(OF(@P%Te$<>CqlXMK)N5AZw-S-FCj?C|j9jFp)c zY}Y~76l`xmPSCk@ZJE!VHbLY;A~9X8ASxm!ttN)96UH`iD%0+tk47 zC}@U?ZMh^O#4*^hKvq$S(vZASLeedImP72N*kV8`s3dWUlq7V=Lj(zQF-s%`#IcVR zMKs%OOxNUEyvFtQ+sHyptJR@iEi+%Pu;=#}3|b5u^?q+&rfvM2R}&3XbQ3r5yvrN>=D`tp%~&gG2IyQ8JAMA$kEw|{euHqMx9(f z$9lcSqsfTN%Trvwa)&e(&m{=1L=;Xy*4bE|r+IeD+R7$+af<1Jj3!B}tpTCQp(jq8Wjl+oo+hh_Ou&*kqF- zQcgz^3Y63mu^`|I0U{x$XEU^YhLK20fl9Fif<&bn(;TNfK7JEHP$-w5!O$}Ju5})3 z5KBuJ71VSpfr#A%Emr1VXZ-XI_rAdF7aG*+HD0>0f~vxM zk6Of`iY!H3-l%bO+U3)GPcSD6E#GDG1t#98PZRn>zWLRc_=CTCp9G0=#vrF^#H5_H zPN~=H+_<>JS+kF*rrdpUf}P4Zv4<{d2m;)`c!~Qv+mz%Are_nTP|7OIl&efUkJhk9 z+w3zxwZ_bp&igx$iSsEvyGSm7_<{FI4hQOxD|>I;{7_S!Ovs?ctCnJQ(`rGO-k2%>-= z{`e<+zViuLRbgeR!PZ`f)%h~3i+QSLooq%RpVfK(T9wY}9?GzX7zHHJxrLkra-xWw zDWeo;u$@z4WE3?+cKQlpHqS}t1VxD`=4zOeW3H_|$HT+>n7&Oa$Q1Nd{3sxfMPyke zianwlk-1gm6SvQ~1SS_!{5&`m4-jBqnZ5XENrbdB*2Yo0KyGxxB*t-F-aIM@FSP zIH6pqu)a9MpTGGtO8FuSv&+=-3Ry)aE6e!Fx&3XSn#J=IG*!U&9WFK2xpVayu3fs! zXxv5;RBS(CIPRk>3QpwWN9S?~E1UoU&Y&YpU;F-V{kAo5h~x9bSQsW0^9H?<$ziue z$;hEAGH2aBpWfSIr8dj_{4DQ$`V=z|xVl>8h1;9NaY&R1j3+7o>i_yP{-3}43GaOV zgsr_NAS*N~^IW}r30+Yrs425EIqK8rfrBIp**og9z2D-)&yQKteaefM**>^Wl!{Cj z3>vG~NydAKszSbY0nfHba#LKo^%7UE-(q9!B5!=>`&27+CSJtW;Xcn@e}?_DQ+#~f zP$5dr4P$AVk_sw{AmK$ZLK&I8%B7xO@A_> zJF!Vb5!a9Kk`zUfF~f+n0c^L1{Pcr|JlTHAe0_>~eTC`r41w=qc`j3#5-&Y}ozL%W zar5#lP1B;?pCHB_ue|gu-OhmddV|?&g+if<=Z`pQ_Xtvfu^*F$G39)jQmKF}NK6-| zSYN+Dp^(FIJ;K;02x2VX#&kTcEG#p2`pA-q>A9$~jWzBguT~|_9w&&g&(dlMbEMH5 zjOg1ovM4jOEV?5o7W1smmH69_KE?7!JaeVNM_VWS?Z0-G5-}Q{P*5UD%9vJPWoP@4mTA-Zn?9cJvv<7B-q9ASnrFJ4v<{xN@Dx z_wOPYGxU%4$;f5I#KRjjx%m87dHbh-NbEn4rW9CTUEpy4jC#G!Vr`y#k3Z${M_43{5+C2`3Ol8iEuN`2q+bk{~hcw-^l$@xqvlmZjh8 zqslT~l%gdui6CP8P$}k66oa`+4L5X9WsN8MTkLdfp8n`lqR1z&Wyq-#tF=I1s>K}5QID}RVWv34#OtETGI6LNAt1}Dh@uGuo=CrI z6GS#gtsW!aCojt=2pqLdR0*~bkrRO*{OletU0EgcV*Dt_@BP6KaeWt2Es&6+Ndb=Q zF|jSeI3ywFf%moZI~xtTKSOS9a4a>l?M69*A-9}}7@I{dLL0ff}k+6ZA3Ak)wk#ldxSnn$f#1n zXp)@k@WwW_h_Zrd1>CxF zoBm)#-x*??CVHk!=*QfB{3#nt8+egR5~q|j6;cJ)54f{=iN(cvb`N*>$}3;txOI#< zHmR4V(N)kSmB#ce{c#U3P4E*RMNA>`@Wx%FZ~Xm#@!OrQk7@dtjsv8GLGXn?+T;1l zvjow(9qYgN-m9$5tDEef9uo&K6W_#jJVHOh^?gJ!A^f5> zY>4R>`r3pz5lG{ND2Z5KyhycBB}sjfNZ^^PH>u2SQc|JQ^N6AWsS`1=Ml3vYn@BIx z+q+BG8uRQ6U!ik0CS&Az^UWVnnZ3Zojk$H{BA;yS@{O;&!S+FiFpWt>kt7b81U4Vv zd&p$q;71;o-6u_DT+77tLYk8angXgUk*3fa+XP8O;0QROhZqaQf(Sx@mk5Muh?EE@ z8I`=QVFn>Vsv(LgK^)T?xCF7t#I<>Pe1aIqeEp@X{LznoMyGX(u1#~{;s*6PLPlhEW{JI{#|%u5gTn)Q6O)M@5yTc+ zrhu%9w1<77B*BkEvWi3+$B2TD#~go8zG;yN-`8pL2q7>QT(8Ac`oWfFgK2 zcXg3kwMvS>pa0|uf_!eSR)v)9r(3-C(u)LfghCckPT5>6Q=iT-ocPFsii(ewL>Zv`$tpzFn4*!VzOhV{t8lXa zh?6J#s5zZNbqd>6dG5|t(pcuh&p)JZ4=~4N<`?Er#XJ+I2ZD&}ImoJtX`8tIm?V-( z(&)U*l}JQsjPJ#yLQKaTFqJXz5+7MkG1Ls%yi7(BS(vSplU2TUXM>qq2{DNgr6Q%g zL1RiGibG}!Q}hEDS&mUf88;R&gOJ&b#PY^v-oE>opMLNTrEG>fH?Q*S)mhG(9)nSf zkGHp3pIao8F;EnN7w_ER$<7XmtD5$*i;1i-i|=f}*Pp&^Oq(J0e8$H?y7xWUf;0f+m$2&qJ+T;$f(RoZ=@ zrw4}&%?V{=nx)1ZpmTP%OWU-`XEJnL6FCuCo}XcCPY6;MJ4&b(vba&q=F%D;Kl+Gl zu0$zQAS>(VjiV$Y4kwtC;m6W9e&xUWPb|k-w#^tpNRX9;W~)msr!cV{oM?jQg_Lty zD#aR)_j}CDRe1355!IZ*tFPSPvwKHutTmX7BQi!A-!_TTgiL0Zoueks=qc5ThN>BC ztX@EqRXjf=4nb046iGytRnkPji(-7+#j=Npg2TzUhkNprLBE3?c+9U|MwCU4pFU>g z`UaJmWsH1@#gzq8HBWaqz_uKA4!5~_`70c^dib#g$ebKM;_trx6%LMDIG#%wMWj)} z%JKzTt$jp40ZB$wHIl?74k1^_GF_^$Ft>^`88H}+=$m66?H|yeIC!B<5K25gJ>dTK z$$2wTO?kA}M9t}x3?1K(5G4UIRY>C$KZ?ky8Uts7KN)lL%56S7Ipp)lTUdRQS6_aP zOY19$iOt=e9Za*!?VGpRJKE!eNB3Bp+a!uDjzkVis=>9*2G_34a&5z4XMcpCNQ_Ju z+i9UFF;k@(isdSrtYXJw)u=AMJg37|&5~W&!`douz zZHB;es5fdDxgw*XNo}S|MwM7yn_+0yG3}6{+2NIEzRcB2m*|@TLhKQxDwaJVOdJ$R zVX?MIvvW+GI9~{|Ghhx&Mg3i^*vj6j3CIK-FUM`2vBTo@-kr zfu%}~xtRvt;Q&=gspc~ja#<1qq9i32LV6RCTt;B*#>|!ry#D+(PEI>KI6Pt08glE} zWiDU5z`GxONXHB~-rr$78Z&lXrt%8CZjWA&kXIC{xjZA=WnjAyC;a@&ukqyQ5v%nj z1W7=ZB_u^a6BSeyEXTxiC-|f0mh{zM`sbH2vhu2FdKmKguic!XP|YRi2B<<7JD89P z;J602Z!8ff0@fsA=#4SE7Hb=;j7%3zHK-O#Jbz~n!zi;hU!but&D`u9vO$rcf>sxemIPQYzK(Y?JwFfwiS+qA((>DO_2Jn5*f+5!ea4ng7{qwtgn{qlx*@SBP}X!tuER<_ zkEB4+$k2CP#&(aAlA$@auw9$E`8vKeBuRA?NgzyJ96RLN#xe(|hl~bYUc7aQu^sW` z_$hl&AM@3hpXJ)s>%8~=&uCj7``sb3B=NM_Ax0!7;r7ZZ%S#Kqwe)+1c6IK<;`?QY2^wlS|8@2#GKy!>|FvHVhjuAVY=>7=~rgHXuTh6Qw07k(*{_ zwUcLO?#vB+Z{ORubE>W!PSvTCkN>Ei*(G2DE}*;7e{=&?-*>+6{k^~N481-htBc(~ z`M;zueBNhE(_Hxk2j7Hl?1v1f)5AH zV-EKB2_uPgLg%Gt<_SZIj44sfOZZ;QR3*jiRE}asXK5zI!gPk&vc}CTbG-J&Yg|~K zVrg-i!wn=tOZ$cMx;+!c{KcSOifck;!uZ;U0Tio5-?4E}ub? zMW&|m%+Aj6#oO0;_V!h7U8y3977rgEAc!#^t*^6KUE$o~BIR<4=Wkx*!o^h@^%|yG zK-YCv7Uw8tE4=Xh7cdMFK~T8<%uR?DJa0g=KOzc!B1I*q$Jnk*tOTS~g<__}r3>e< z`vXJ*n&Tc>-QdjP0)zgD#f1t>(*+vMIz$1Y98zyL$s{w3Lzj#ZQLU~L_%>Vh7HfC! z^KXCsRTdYPdHwwlhy{6q@{$BDOiy!S*_0AF;@D-kSx1*tMqa?cbrFe4=n^NbCaHu$ z99#IYkK>JSJsW4(X3*OCo}AQ`kF23bQWZ%k5?Ut9{DQ*Ve2Vd~M`gOel~Cm9SmVr@ z3P(q6@|hI9u+CxQm`IFq9ghfy_CV*q`yao{@Bh}9$)?IY*&H)l)#&!dG+I7`zQums zW~P*-)wP(P$ujn19&Yw{v#dQH^78eoeD$?U zG!AVpU0q-_8uO!f?((B|?{V|WC7!u`hS^yYfHL5@5nq4hHs$gHvZ8Q)rNH+7kV+-X z)r&KjDG5#U$?DJI2M${YBfj;@86-_(W2a5eviZ_;^DHe+bNA66kGCHXMq@57oWr(5 zig^=JcKOyLUw9@Ol5in51^Xxs@x_nhoyl9`bOr&2u*{^X%#ZcQ$Gayb;UgDbl76 zq0D~kkV~u2GM&s22_aj7jq3$ms#Msi9nl*HOi2m6P#_`8h@wL{8bRz5hw4Yt?9|^7 zbMxi@Koder8HJQ75RZMT)dbaYg={84%G8i#fo|VpYp2FR?T}`7!13`B2~Fng+%z|@ zoZ;hpAF+8<=kdl7*;I*ACBxofKom=yTh@`p1hT4NCJi2Kc39tOuy*eWgMp1AC7GG2 z((c)GEep%>2|}OR@c~=Ad+eSZaqrP1l8Qn)spGjKejqVZnV~v+j!xfWY=s!AhN=k& z0!XreBnwPWC8-n*E}oy}*&7R7I-e(>kuj4ZhAuN-O|v+cXZ1`8Go>;b27K>_@9=cv z0lK-wNqv(i8xP24iY!)^n3_4W{Iwm0} zIp}m43@lFiJz`&B_3~NXc;{m}{WcK>3DGBEq!0xSBb8oCHA^CVqz}LUtnYpIPO}6QG_Z@kg=j5APXXyWCB$I!T?#)=#4sz z`bT)}z5hyjviHArU;Ofa_@9}EB2HCg2Aw{&rpNL^5nTr<@X{|Rk;9bV8Z z&Cl`GFFwoDod+0FL^5e|XZ<1W(C6IpEI}-@wtmQ+2fJ)+>|rV~vL>;<`G~#R6I@rM z(LKQ)cM&3)ZhwI1+X#}%Z+`2meCfstzy6D_F<)8Yq!n|}ZjwtS*xWnj$&*Kv^Lfho z1l4kaR7xZWIh}&_IW_73*DH!9q2S8mJhv{N=Y?mlVUHD5J>sC&B$-Ha>*g1j%4GO- z{T@=}6NMlvGUaR;QHs%2mA(BPzVmCpO(vOPwmQ$o?hbd>9P(P!)xvPK%_XA_*a{Jo7cy zH$DbL6j3D(11`;;<;>hNu_OQ?>A+4!gy>3;`y*`bhWf=@3L^-CI$+EV-ffqaU z#~wjwQ_AH~BMCEKp_(s)ROG1E>TZ2M=`e5;`;ej7G`Fto%C5Gs z%S>hSq;#E9KF8P@P)KDNIeq$em(HMv;E!nR9{!77r}aZg7GU+6Kgi5f&0j-=LEq=e zL5=6mr%~iIg5YssF+!;&;zy(dPIW2?TcratuA6&ZPK|prb`9#X_H#JMr$CF zO&RPQcj$B)7`jYSi#V(Y*y8{}DxxY0cJ@!uRE;06izWYf`ISGBn$yf5s<{maID3dA`8Mm=u<8&lFtYzl8haN{9CWQMmC=!tHu2E!8%zp zO}*Vl5hYALODUxixe}@pqib1i-MB(RPqDT4gw3Nv5~7CV$BaXPopy^#HUmM#R54E& zjG4~Q;kZK-MW;9J(zgbT#zUl_OTD)9-`k_XBbiua@7`a$eRlqLDU}p5Ns;rjIUa6x zS-rSGt>Gt)PeN1q{ZyIn z{@``Y%q&7QM4}F^jV6`ZtBvTi4{6#R1O$XoWH9Vv4K1d!S=`uVXtznoI)-U-asC!a z5X57Qq>48<;l-QJVU1e+^B=s8;|>u;4e!J6V~w7nT0KM7RGFSq&@_<5Ng6a~%5*J{ z_%uoNIc4+b?g~};8D}r_We!h9G} zG9<;ATh}f_=#w&&WK*+Vj+zfjabuk5JEI1L8&Cs>KyXTuY8I1 zjR-F|L{k#%?(EUGJkBo9a(J}M)7?E}UEyHZM$-g*S0J58U`Inn_7FiO&kw9MwG@ZQC3k_j&mEkcZnH?9qtpS8lMme*j^?>Y1Ai?HVV2ix;k4=GOId z3^=^k| zx6VwZ%E%e8w6MZdsemF&d~$D-RLZ~_jVT%_TCPXn1|&6;R)2sK1PDS%EC|F1j2(-% z)jo5h8)-U;0~TGkNp*j42Ze3Yx03Fl}&VPRDn_7ezV)pL9~- z&%gfxvTR~XHUU06+uO8y4Ys%U_~iZ?{l3kIA3dON)j2pGVhwB-r%Xi2=ly7?l>M9guc&d%`(=a$YgU0o!X zH!&2M^`}pmsm{^scSxE^hL%f4H*f<7Nfb!x21z|h&*@LN&k=O`9UQlV)v5pUL8twF z2$6|JqQGE(_vkNE8S{@;&u4k?sLpgrA(coVivqJ{sP$qB=A`H|m(F9R(!_qBZ+`7r z(n+1adgC3w_s%`s&_j~$Q7#m*dnZJJhoXvn`;}{)J$IQGZavGN|KJC#%*^oBm!4zu zu*IGG8`P~KNGc){&tJVv5R23r$80>k!%JU$mGQXFwR3sa9(U=DHRg&im0hG%&{?UT zVdwZMje5xQH(q9B4|%fNK~Lz|!w|wczH3vQFH-MW*rO&dJUfRX{eQ&*6YlPC7}FjE ztnYR??(~_PpCxHz(ZrZ++GXz`VtMu)X+vXXc8U3^EKAGtgkHoCU;ioBuU_V@kM8i& zt=qJ_CtSL69wCg`IUKTcu!(|-tV^5>hIr1HhSkR!2S^CCntRMIpP^h@K$YrbQWX^0 zfKWgbb!KPhXq|M(WekXtXmW$Jp(Dx>&6bU;#Ym#a*zrioI<7ZB2qQdyOgwP^GA0CK zqzPFv!0o-YGk@XV`J;uol&;7zZ@#~S@4DQ&RKzqSqEH};V(vVu6ZjE<>k?tIJeTF4 ze)oH*am25E`&9zpqnORIx>)AhUwf5TUVM?+=>%`xxzEn-F3nn#FTeN#>sw6%@-u#rM=@|^w<4Z4HCPwA#LLNmHPDS76p!L(AlUIKj z@?^)O(HA>)*tcVqj#w{j%aoI)Eb9$`#y5ykV@(7?4OWI8l=n| zrU~hc$tNFvM57y$GgByv#$XuGYxjA&`w-h!Ddh5GGBb30CtP2>1)|K;?L7+l5}o!Q zx36C#jvAmEoWL`G-8(c$aGzuhXb)aO>KOTwIysD_?#NS&>+nQ3#_5 zNf7XZ7+Lx2!1XwuAn)2EX!nQg?A9l<`9@t%PB!WGI_%VUX!k8rqC&A!B%R6-NeVr? z$@-qg?0khYO9tIRpQX8JYt8U`k4^@&7 zrHFJO;(9JIE|Kef_poQhr}!`=L7brCVnRQ7W1AIsv&%it~%p zyztyIp6{@I&?A$o0ZB8q%pej|uIOwZ)ET)xh$?a@v01P4+u!&qKfS-s)8ixZ>1iZU zCI}-IW|t}El1P$3si-kCmEibfgpo|~JHPfCl~RS#u))_~d665}ud}jLnS^|Cj3`dP zn8GPM@#jV0U=Yym#)wfwt2gGs+8XDUX1RW8hB%JcIed&gY-1Q{R%fdOkwiM-Gju~{ z%JY=+3S&p4TAIQdwYj^t!EtX$%ADc)>JmFgHq&L3N;!ipDkL?9bh<)PwfR5ac!SZ< z;^muP^^y!GKHJlU0DpE%Ggz?)wdaXBZ?W5z!{?^)3mz-Mgtd7 zaL_c9XK$Qi@8AI6gW)LPnd^CCQKZv{rCF0=UMG`I^6Jaava(pEkToXs?9cJ(5XUj1 z2vHOh1Tlj_#LiL3=AOml-8K?2rGiK*o#VpF6oXMr?YP0x+&s@-xlX^=V!B#pYk!wg zX^~W7(kvQ9F_m;-0==6P3DUmndd%&mnJ|Ur~sG32wP{JL14z z;f33isbr;Go?5HV%uJbPvrim5l*$!ERYFw^qR1u|Bw~DIA%U4L(ziOC)DP*{V~VCq zCRLzkcTf-roIZiu#_bRPr(S#i^;3ZCXUG`gb1y`QB+&Dlh1nZ_Z#WWZG%WhPKI2g2 z%xnr>6ObhpH-=0qjV6N>TfF<}K7rrk{>Blyl4NN%kLw3y%nW`UBPUc6nu_EP8F@C& zSYT);s1%dra|#Qy(^$4cIQF=G?FuhE|3&tXx|DMnB4NxAKYW+vN`YVggN$edTQfs!!Wy?5$gk|-~rFAN~ z8CK7oA)iU|;L#^IfL@3OZ3{nY^VkX1P%nHvxdyUoeD`b)>#-2dhQ0VkW z6pIryOucyo#5m)S>1q+r4;VTQ0xHNNp&JnUJ_CDzp(HqJw}^s}scacP8Z#Pq5PTcQ zYU8zQzikbLo*+1%zcfuOC2^gAt^GqZ8AOsq zeoV2XQqE^+^a32K&$X*l)SE*-UVF$3H*fQ$8;ktmcYc>vqr>@ydE6)@VMtVpX|frW zqmwotfBKk9=Vpl_0Y3y;5+-!5cyh%>;lv8IZIS*cV(bVAafs)8eDdHCM~w#E?f`e( zWM#HQF>liD_PBihHlBTgC~Ca_(ObAgVNK7&zAvpt|*Z1;f#nbl{AWAwytfDIk0&hgKZ($@AqHstYxL89Af7JQ&+V1*4j^hym z;wc?oK#~OMvx9OX24j)J^b57Z!t!_eJrCb@@WTOT7mJuF6+w)M1&IiewT&9@eR`ke znKZX=Jj2D60>_O$P6V;*A*vcl9n#4ZgPu#ZoMr1^pPA_?rb{K-!#ev%Ju0OV`MiRn z7~Ff@Woaf&Qp@t`+99Q4mifvVT*u?#(>)UDEahy9av@2(@6+kIBvgm?pheOsFjY*_ z97G%*+9WlV;V|O3?$hjqbcZpuwokq3@cKLN@z4JBkNNP<$5itM)pCXR@B9@+J_pBJ zl(KmYMP~o#5Fu`}e|${0x6jt$A#*cRXqv%r7&A3hM%K~{2Te+q86G@*!25UC@Zy+a zF-tk0AyyM8fp`4gt~x=*?i^VV%78~Mon<_BdHual7!EydUY*BGiJUY? zWV0E@0qoa0gqFpHOUwN5-3Pq(!YXDeM?RC_sOcb!B9biA9R}REc!8y*3;+pLB8o&B z?U;@I4j(_*XLG;Fr*}69JfGde2h2Wj~_w0xG=fAT}7N+t9}hAZcnx%;Gs zAIY?b10L;cqlz(0^Jkf#p5vq&F&x?y(h2P0KFbSLG({yGpYRuNy+<6z{Ee^vA}Lj- z-3t(d2s2+{s!-&h-o^GHh-CWx0rwy75XBKc`pKWMzWW$OOR_k-Kq{r6DiT2?kT5i2 z0g`$Q5jbv0r`0BkBZN4jKd?{)h+=^_@ab4bB$7$`y%QX}$H?w}&{<#m&%@|vxepGtM ztf|uK2K4$aNj=Fc&#&US0uhsJogzxKdJcxBFgImls1lYH;rn3Q5QUQ#wE1e93(F<0 zouA_D@&d#G)u}loF@d2P3`P!t?=w3+N5-6`RGdQ<%9quw7+kD~n4W_2f za#%k`QzM4M4xR1^`O++#>zjQ2<(K(~fBeVXzW6*hu3o3nZSux@AF;5wge1DG%wJ%t zlEx51EGI^hWwtk;^6}bxTsnJ&qvI0}>-#KLmod!@s-R=&@}#IG2G@y*!U#WzX|_qMGZ-AxACK|J^}jP{);2za#tXzCetrUolT2I; zkpQtUQO#Qq|Ha0&FZ|Be^ZCr&R7ph&A}Un_L4;<}ZeKqQP}5F*BI#3$<~=&C^w ziA3R~B~8{6c%g(UNo2EGj6@d4lUaMTj;M?nyAtzrbNIeMv)iRqnBv;%E%KQxPq()b z0vl15=?~hRU0x=u8GQVBgJ-WS^5KJZWF>>;jA%DQ9KXS}b60q}w@22@@WDqP@UP$c zK2ofaHWJiYZSH*f5h+vW@y;U(`Du!oEUOpJvv;(QD(T3wiXV7*UX0@f^oK**tsb7| zuz&oBUcbwe1c6Ge001BWNklYSgO2Pr|Nlw|*)Mt>|bc4MZCCZ&SHr;qBi`VOk0;|3yzCb7BS;_21_ z=}ZFrkmKeqyN3?_!GO@QuMXTOKGYn)oi7YFO93R{E7>zuJ1CM%r8`};T zjQVtkBgT$BDSZ=TTzAN5TxZnpVp)A+%m3Y8>u?vM&%KbCNef^+p@&cI0zsHqqES3C zO9TP7p8TubE6@D;i;W{K@dHNAK8^N>Y%T+lNDzh8+A+t?8e`j`T%D)Y z9}~DDv$IoZVnoVJVkQlAqlD`^>>bzn=*~|V+7XWHFdRD^)erF^8x{EJ{T-4GBlEO^X{>u2QSFSg00=qkzTv^XwizW_tDc;CT*07~?nrzUwk}JbX8#Yqe=Kj)+5_*09Fdckm?{Llw~_ogf&|9c*K@ zdpM)3(0X~BYPa*+<2~sSMKu`+rFI>F-hhZ!W2~{GN zB%&ZBp{gYGm}b*KkY&t-h98COZf&u)-J+N&aOLbGX;WvmnkAdgVS6_H{+LR(fa6*$ zEiCc!^H&&+U9O$aq9`J96cfe}hcU+um%D2_ym0#rqA1`80wX6T2po2{cj#Fo{+Iv# zAMo6bSI{*BFNpCx5X2ZgVL%k)qc9lRxNb-)ktJoCw7XsI-g}?3IUbOXQ1871ERn zlR?oz6f>|wG+Cz6b!qh+0>@>#n8k9&znE(Qrc##|yEY z0h*RW#G&1=n4iCbnKIbe-e6$4_(4bj#HcJx&vWVQC7$eUku%d&i$zk|B2g@|f3l0~ z`E+fIp&g=$Nd}`9VHA-wbpR}@$*|eMZjb((+QFxPI;nd4EX0pb4}ys)h5(XC8j%Qu z7%|qF+!d1nkDu+FhlhXi;fFhOC|~F?3=yRpaAN zKVj#ji4=(}&QGJrBBmi>7&?PtOs_vCtw%iCI;L36U|A07WEKPgSrP~%5k(fLw)LeMeag8cvM7*Fn#@d{MHE!JcAJ0ovRpVKkT|rRJxz zOcfL~MJJXuHn+BU^X=EUyMDml(GjcXFOW>;$e3w#RlxT}uCA7_tR_GC>HAET=P2l1 zE?->X;o}W1UVNV8c87YS$?o0}L%Yk}M_cSSTcnL7U;FaQSXQ5U+rsw;G}}$~4vwhz z8w`4V-oF1aZm7^34-qAiTq4O*WeP+IRg74gU*+g%2T9WLJfHrcOAv*025nB7eL^up zmSO~1MG_^lhJxo0>2>OKPEK}gXZV|=VcnmEbrHhku|IvxCjxM?0Yn9aNkb+=JmCmW z_|l(W3gIC3)A^-)#mf2b_(4q45J?&$_RwK$hXg@HW!glQHNq%D5KMOWHxZB!g#^`d zmPAq`m5`_ubL5LTf>2`UI>??)GF_zE9U)07xwMQR#)MIf8wiB4%v>djs!I5AOcX$+ zq+=u%)}KD%_T?+Q^1_#ys$4=-VwUD+@cocNR%d99XtaA&D@Dph32PLwe_}IL&`Bi% z-u~$ZnyT^a&1Z;RA3^X~e|(pngCSQgOkpNX-u?J4ap)385i{i}p6(oQ>-JYE7iUVHo)jY6kAARw;p#7B z%;L-!4i7uHc8KeGoYb2LB2@A^m2!%q6H_e|X!n{FGBSpirqcO*Iv2KkKTQo z^K)ldT3Y4C)eG$HZgBU}HVG}u?JE~ZCS$e_kNDavuaHjV5XBhZjoI7Z!4JpisXWE> zG_%zOG(FA!$pO}INIpAFw{uLbb;#by0j0uw7Z7A8Va5@MoLk2oe~;x?a#h_S%^y+3*D#+Bdtc4>B|a=2S3 zX(m7j3B8D|{R5U4XAuz)MUgWL6`IW^g=~(I@6&8MlnXk^lt`!V;y6BGn4s0E(`E*$XDucW`>_pN4+|Kht_b zOe}B;k0zeTzc@ZM3ge%-f~S8j#%W?J2*zXO?YXnh{MO7|R&VzmbUneN&26@hJ2Yx1 zd?bq1EY=%T-vcDmYHeR6*gW zX`v?)v#pb+hJqpDYB%Z$|mL99La>j_Q6vM zsVd9!=ec_B8P2Y(lFDRBr;-S#8A{iSP*oi<76=iM1%>0r9-cd41*KGtAn?((BvBMm zN##)_4M~WJgAt9+UVqrCeRKEV?u=HADRY-#17uyFZTO8F$kl1{DWanu{p?T#oH3XI1A`v+~}NFZ&d z>2zI6S(S1z#lxo^3b`Dv4_#}(POXU|OvqfJ;B(xl)9Sbkhc>#Aq|vh3J+RPJo%t!9 za!zKdQet|hj3}Fo9GB^Gn)AzxTsuF*{A`jS7Dyx|L`h^gayjYyw2t?2MnmjCpYxf-rI@ z7OUKUw8_x%aqR)JXb=c8p65_+?o(?t$!Dg>nq^c?CSRm($o*Vk}6(Z9d(>A!g+Ci*-H zK3UJ0Sokcomroz(Nlri@LYj!dpDUOU{GZFEPojg#f16a12!g@W^*?)1D&H<7i%ZWX z6p5)>llt+1!_kOgr$g2-C{;^5c>Dyb-^SEc8m%7Z7K>P;F>C8j$rlUs1_45((C##; zxB6@yZsU63x*dFrbNV~ zh0OOOPFh{=t*x`Xw8FVFRn9IiGgHYE#R^lEGR0Dfl$k{ob-b}dsWL|?Um{z`fh18V zW+`S&5(bR=BX$oqk&MD=okD~k1PDX~VML6CuB9;(8R8%$jy)`Az}WRDnbQoM7NV#z z9v#ze?Eeps@BHMy^#f}njB&dLd zh)5I*$dXPF1SIt|#bjy1b|A!cdo*h6|N7~t@BfZHZvO1K7bdx#&r-jq*3Qo~fG`<` z%|rx>pWh6^=U2ziibOtB0m5IqDn#NiW^?~f{%U3Um%ib|=G4k!hMDOsa-h>6bhx~- zh!c$Y;V19n4+F;IKE2j}mE{?xr_!ACU1}#SR_2Nvw_Nh24EIByx}@cD5ga7sA5)3`=ttG0g<$7mFCW zO4?8;WF@kuh$IWBl8B+kh>}D)Az)}CgfWfIh*oRJ&S8^QZ-A~D%+KU_xPDA$VDtLh zuj9r!a`^%~hZ~e~3(QofD3x-AkxDY9Fg-m_%FxNA3_RCI5JgPWKvz{p!x4TMG8zp) zR3R3LQ4j=hoiPJzKo|>n;}No^ATcS=x127vW6`WVxO>>FeY?HC=Kg$Tv+()%`11(h z^G)%&(I+6HAPC|lz502~`2TcuZn3sr*IoaOIlph+_g;IStFJkZ)<+x2Pf zb3gmGZr}IKImh5(&UHC?I$LM$z1Fwp9CM8S_>cb>vnYhEbjs#zz@Z0*ohYn9MX{j4 z2Od5A`#PODl8gpGFCz8iSpb_*O^QP zESovrge#9dz_?fOy*IBj+MDpbSKnqj-DlOT==HN;b5sfY({p%LUb^}QA9&9dW=*7x zk=R8r+!0c))gw)j zQoHN{)&`UAoHaM@Uj5?ZS3df)izNqVD`h(I40}T+;~q|G9(&hgeB{r5fJ^5FJomz@ zJap+2=MG1_=aB<~ubHisle1Gs!wJ*r1*V5%d|mVS!~4`j$AcI5Ie&3P+rf0dVm=RC zJlCVDg?{Cj&z6iw6{CT$S{ZjvBZgx<6ovz`qPZ3ZH4OS&iC{7~nZvMhh$G*z8-&m$ z7L9WL+=PnB5}Z;*8VPJDl8m{o2{l ztxtaU`KNE2Dk2V*P;i>${R5nN%qiy91t2R$pv#B^%yn52gygnq6D6OSoIaRrZ3hIG z&8gUhQAG93@BHc6Tlc>B^!tA3XFu(H|KR9og`wQKbw+TYqxxWO46PuRQzZYpPme zeAyex&9B-0dvGZedA6GXD#jc{F%n+H<8c}7?zBj+b>o<04Gx4U5O!*Y+qkp5J3D^! z#`k~gy^sH;pWK@sT&hv_1{I3OR~2XThP_drsva;IXF^CFjC#-?R9reY;KJd6gTn#6 zD#vhDNSa+)mke*7{UP+cunKVRIMQ^`#K42+28;%tsxqp&=3u|ZIbm35FBX$7_tN6S;% zbg|)qOAm2;az;vy&@S0Om~eP7B&Lp(j8R>qrZ_-+E_`WdX`4U@4XeOC=Rowr=vVvR6|6O=aU{2NPsqZ=y)qSE*eO zGeu=9okp@Y(5)wD6ru<&BOGA_EJMod2{uquIQQ;!_;)|^`+sodu|MXF9CukAz-v^{0zOh|n~VgZ&&;$blJDZpg`>hDif(+u^29aY{NrEy z+=J&n`To7bhky9!ctuYP)5K^r#5-kgP}8)As~xI>s~nXVh8|)B@o7`B8upF4_Ebf% z(uS;T8I53XSkneaU0%C;Z%KdHCne$fQNUN8O2F57G0qk_MeMr7;hu1~@9!Gq z+P2~3!9ljkm9diRg#(z0VP7%=Qt?2jt$&0EAds-DA{;GE_~_kvpP-02duQSm0k zypBb?Xn0@KcE*jH$1GP3`+Eb%dwpuJ#OUbvgnk9af`k1&RV5tm3;ScwbmFOdj#Xfj z0ma}pr^f+Nb2qdlql>`VBmh1kroB!eJ*Ps4{tKWX6-Fj%-r0i== zCmWDwTko*W2g)Iqa_VKR5!kAN!ORyHDT6=RCWT#nWi5FwBe)bS9gRbwt z`a8cHdyl^Hz~v`?Vmcl7d&53eZ8$2gxxr+@Xk1|eld)$wfWv9cWFqu?HS;Dnw@q@dCTZu5do3(H9)v)*D-hf~|DeZ8+nNo4K!(c8~#~ZNrTfV_3%^F?jp?|NEtz z$JhSX{=xe{?rnV80kQJT<||H)SKPdDo9T29e9nuPO#<_DF_NOEYZXGQg`aZk%sF~BqojD**ummXzUQ%Yq)d-ydwL>B{2avwzM47G@=&^1b3O(5pEgxzdK z*DP7KbBIxIPwXrvUWD@9GgDx!9DjbpZ$v0MbsW;2$HIi$etv+JyyV~$t1{_WyK zKl9wvzx+;&%ll&dHjm$8ux;JO-8bzlH-0*hoDQzVplhDT$Rv?>;tPDpl_I~R9m{X) z3Sh7eVYJ}|*1v1nTdW_*QDNmo845}0=03dg(&wI;t@Qtn$5%cvoE#ns!tv>n7hb*2 zjoWwGKR8cvJyxx-TCIr5Ga7n&PH02IyZpI*4|VN1p5>HuJgP~~u-JSPyW+?+2dnIfA8bt(LhpYxOdWV zd~}33p*QT&hU|Ol%4Ha8rI|r87{q&OgyTgbSBA847CEtEDwKD z%gkSELJXM#)@W!{LQq8WeLtE!Se_ti<_MuBgxo`Eg|TZxOG<*Kgyy+hm{f!=a=b`b z2rO3(^JT;AWJYLPn#Da9^J8wG-LqMH`b&-3-}~z4f9bW=@|_)kS3uty^^0u1@dg8I z^ZfgVT^J%$jCM=^N+HEWvu7~yNL_jYLO%a4=Kz^T3`jAL4LP(15VmbJ*iAp~FF+U$ zMMZEM_-M$9bgzErbANFA?pvR&`|o|X`X*#pzTkaBt>5Yl-Hs{RF!4UOyRX;R*@ggG%Y0;App)V;${80z-lt+5YcFDu zZ+0hN9k+QC+hp^7Oadbj;#xDZR+3m2j&4K-o5F(v07`08_RC`mC^X}d-u;j^k}Vl*6#W9dyIQ7GiFIPixM>J1zj2Amff7tG|GIwpx5h@ zEFuXMrR{{E*@J1yDP~LwQAe_hXaUO={a~Rx000K6Nkl*GRjEJGp$ zM-x&BcS5sj2PTN#YtXJ5&omwtA+-~=y6#32NQE=jR2H%#wlKaQuZra0&L5O$N z+F@^7#5dH?T0Ce#l^MX@#u1ZZRf^k)$+RQ^dH!IYwz$Qp`O~vYcCPgAJHdJ5!=L(R zfBU^p{;hw0?$Sda?tA0%3u~flg$Ea7KQsFFR3_xyn1tF%0sn5bp%Y>0?p1i#_G+&6KCN`w#&@~tgiP%L} zO?J7OA}PUae#ZRln0u#pn02eKYqEdyop1l<({F$8E12f*;yEC3RNA{dsIFCf8wr0w zaodo<8haHR5cz?M-_HC)T^pDFz@!uq?|u2%mG}?hG;bdL5<{#Q;La#D$#HpIIvo})onXr!F36jZRf` zW>9{qC*w!I+eIj)e^DW1(t*6~(0L2xf zkPTC7k_vBMLW9epO06in%eYvvRMt1IiS;qD#*P#ct2UAXq_|*!oz9x}ca9eK{^vKJ|H7BvzWRTN@hl$#l}(T> zGVAT8t#f(X9aCEj#mSc0W6Q>JA6Co<*YXL(OiG0}q;{U3B+NOm4)4l6fQ6ICVA8LMVt5>(oN^38 zuJ@9(!zrw~h(_7u(J3g&l(ut()tshlXjd(y@JcfI{g_r?e*U@t{<~MNehFPH*XjFK ziful>m$4OXm~1%tb=tR9@EH4u9AmozuQjMM4-(7!N?9jmcM_iIuB>134QSZ?jG!3e zz1x%#O}79~a3o2YJ;I}+8EKN!BM~VNNwC&Ug70kt-?i}qPfi*GGM_(so?|qgw-;c{ZVM=b7%X{o7%Of zp8AJx+ 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/defaults.h b/include/defaults.h new file mode 100644 index 0000000..96cb241 --- /dev/null +++ b/include/defaults.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_DEFAULTS_H ) +#define INCLUDED_DEFAULTS_H + +#define DEFAULT_EDITORVFS_DIRNAME "base/" +#define DEFAULT_TEXTURE_DIRNAME "textures/" +#define DEFAULT_NOTEX_DIRNAME DEFAULT_TEXTURE_DIRNAME "radiant/" +#define DEFAULT_NOTEX_BASENAME "notex" +#define DEFAULT_SHADERNOTEX_BASENAME "shadernotex" +#define DEFAULT_NOTEX_NAME DEFAULT_NOTEX_DIRNAME DEFAULT_NOTEX_BASENAME +#define DEFAULT_SHADERNOTEX_NAME DEFAULT_NOTEX_DIRNAME DEFAULT_SHADERNOTEX_BASENAME + +#endif // INCLUDED_DEFAULTS_H diff --git a/include/dpkdeps.h b/include/dpkdeps.h new file mode 100644 index 0000000..424fbdc --- /dev/null +++ b/include/dpkdeps.h @@ -0,0 +1,91 @@ +#ifndef __DPKDEPS_H__ +#define __DPKDEPS_H__ + +#include +#include "string/string.h" + +// Comparaison function for version numbers +// Implementation is based on dpkg's version comparison code (verrevcmp() and order()) +// http://anonscm.debian.org/gitweb/?p=dpkg/dpkg.git;a=blob;f=lib/dpkg/version.c;hb=74946af470550a3295e00cf57eca1747215b9311 +inline int char_weight(char c){ + if (std::isdigit(c)) + return 0; + else if (std::isalpha(c)) + return c; + else if (c == '~') + return -1; + else if (c) + return c + 256; + else + return 0; +} + +inline int DpkPakVersionCmp(const char* a, const char* b){ + while (*a || *b) { + int firstDiff = 0; + + while ((*a && !std::isdigit(*a)) || (*b && !std::isdigit(*b))) { + int ac = char_weight(*a); + int bc = char_weight(*b); + + if (ac != bc) + return ac - bc; + + a++; + b++; + } + + while (*a == '0') + a++; + while (*b == '0') + b++; + + while (std::isdigit(*a) && std::isdigit(*b)) { + if (firstDiff == 0) + firstDiff = *a - *b; + a++; + b++; + } + + if (std::isdigit(*a)) + return 1; + if (std::isdigit(*b)) + return -1; + if (firstDiff) + return firstDiff; + } + + return false; +} + +// release strings after using +inline bool DpkReadDepsLine( const char *line, char **pakname, char **pakversion ){ + const char* c = line; + const char* p_name; + const char* p_name_end; + const char* p_version; + const char* p_version_end; + + *pakname = 0; + *pakversion = 0; + + while ( std::isspace( *c ) && *c != '\0' ) ++c; + p_name = c; + while ( !std::isspace( *c ) && *c != '\0' ) ++c; + p_name_end = c; + while ( std::isspace( *c ) && *c != '\0' ) ++c; + p_version = c; + while ( !std::isspace( *c ) && *c != '\0' ) ++c; + p_version_end = c; + + if ( p_name_end - p_name > 0 ){ + *pakname = string_clone_range( StringRange( p_name, p_name_end ) ); + } else return false; + + if ( p_version_end - p_version > 0 ) { + *pakversion = string_clone_range( StringRange( p_version, p_version_end ) ); + } + return true; +} + +#endif diff --git a/include/editable.h b/include/editable.h new file mode 100644 index 0000000..39e1174 --- /dev/null +++ b/include/editable.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_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.h b/include/iarchive.h new file mode 100644 index 0000000..b6cfda1 --- /dev/null +++ b/include/iarchive.h @@ -0,0 +1,163 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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: +virtual ~ArchiveFile() = default; +/// \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: +virtual ~ArchiveTextFile() = default; +/// \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: +virtual ~Archive() = default; + +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.h b/include/ibrush.h new file mode 100644 index 0000000..3361652 --- /dev/null +++ b/include/ibrush.h @@ -0,0 +1,126 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 Callback BrushFaceDataCallback; + +class BrushCreator +{ +public: +INTEGER_CONSTANT( Version, 1 ); +STRING_CONSTANT( Name, "brush" ); +virtual scene::Node& createBrush() = 0; +virtual EBrushType getBrushType() = 0; +virtual void setBrushType(EBrushType t) = 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.h b/include/icamera.h new file mode 100644 index 0000000..1ba28e2 --- /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/callback.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.h b/include/idatastream.h new file mode 100644 index 0000000..674c9fa --- /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.h b/include/ieclass.h new file mode 100644 index 0000000..3411ead --- /dev/null +++ b/include/ieclass.h @@ -0,0 +1,117 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 ( *forEachPoint )( EntityClassVisitor& visitor ); /* eukara */ + 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.h b/include/ientity.h new file mode 100644 index 0000000..9a6ecdd --- /dev/null +++ b/include/ientity.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_IENTITY_H ) +#define INCLUDED_IENTITY_H + +#include "generic/constant.h" + +#include "string/string.h" +#include "scenelib.h" + +class EntityClass; + +typedef Callback KeyObserver; + +class EntityKeyValue +{ +public: +virtual ~EntityKeyValue() = default; +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 int getKeyEntries( void ) 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, int index ) = 0; + +virtual void setLightRadii( bool lightRadii ) = 0; +virtual bool getLightRadii() const = 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.h b/include/ifilesystem.h new file mode 100644 index 0000000..d7e128e --- /dev/null +++ b/include/ifilesystem.h @@ -0,0 +1,134 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/callback.h" + +typedef Callback ArchiveNameCallback; +typedef Callback 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 Clear the filesystem if supported +virtual void clear() = 0; +/// \brief Reload the filesystem if supported +virtual void refresh() = 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, bool pakonly = true ) = 0; +virtual void forEachArchive( const ArchiveNameCallback& callback, bool pakonly = true, bool reverse = false ) = 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.h b/include/ifiletypes.h new file mode 100644 index 0000000..25d2cac --- /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, bool _can_load = true, bool _can_import = true, bool _can_save = true ) + : name( _name ), pattern( _pattern ), can_load( _can_load ), can_import( _can_import ), can_save( _can_save ){ +} +const char* name; +const char* pattern; +bool can_load; +bool can_import; +bool can_save; +}; + + +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, bool want_load = false, bool want_import = false, bool want_save = false ) = 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.h b/include/ifilter.h new file mode 100644 index 0000000..0769229 --- /dev/null +++ b/include/ifilter.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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, + EXCLUDE_DECALS = 0x00100000, + EXCLUDE_GROUP = 0x00200000, +}; + +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.h b/include/igl.h new file mode 100644 index 0000000..cb7b66d --- /dev/null +++ b/include/igl.h @@ -0,0 +1,2816 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 "globaldefs.h" +#include +#include +#include "generic/constant.h" + +#if GDEF_OS_WINDOWS +#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 + +#include "gtkutil/glfont.h" + +/// \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 )( const char *file, int line ); + + GLFont *m_font; // MUST be set! + + /// \brief Renders \p string at the current raster-position of the current context. + void drawString( const char* string ) const { + m_font->printString( string ); + } + + /// \brief Renders \p character at the current raster-position of the current context. + void drawChar( char character ) const { + char s[2]; + s[0] = character; + s[1] = 0; + drawString( s ); + } + + + // 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 ); +#if defined( MACVERSION ) && MACVERSION > 15 + //Snow Leopard 16, Leopard 15, Tiger 14, Panther 13, ... + void ( QGL_DLLEXPORT *m_glTexImage1D )( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels ); + void ( QGL_DLLEXPORT *m_glTexImage2D )( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ); +#else + 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 ); +#endif + 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 GDEF_DEBUG +#define GlobalOpenGL_debugAssertNoErrors() GlobalOpenGL().assertNoErrors( __FILE__, __LINE__ ) +#else +#define GlobalOpenGL_debugAssertNoErrors() +#endif + + +#endif diff --git a/include/iglrender.h b/include/iglrender.h new file mode 100644 index 0000000..cd1af77 --- /dev/null +++ b/include/iglrender.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_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; +GLint m_polygonoffset; +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.h b/include/igtkgl.h new file mode 100644 index 0000000..b445456 --- /dev/null +++ b/include/igtkgl.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_IGTKGL_H ) +#define INCLUDED_IGTKGL_H + +#include +#include "generic/constant.h" + +template +using func = T *; + +struct _QERGtkGLTable { + STRING_CONSTANT(Name, "gtkgl"); + INTEGER_CONSTANT(Version, 1); + + func glwidget_new; + func glwidget_swap_buffers; + func glwidget_make_current; + func glwidget_destroy_context; + func glwidget_create_context; +}; + +#endif diff --git a/include/iimage.h b/include/iimage.h new file mode 100644 index 0000000..7f355f2 --- /dev/null +++ b/include/iimage.h @@ -0,0 +1,73 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 ~Image() = default; +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.h b/include/imap.h new file mode 100644 index 0000000..396627d --- /dev/null +++ b/include/imap.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_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: +virtual ~MapFormat() = default; +INTEGER_CONSTANT( Version, 2 ); +STRING_CONSTANT( Name, "map" ); +mutable bool wrongFormat; + +/// \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.h b/include/imodel.h new file mode 100644 index 0000000..d047e90 --- /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/ioapi_buf.h b/include/ioapi_buf.h new file mode 100644 index 0000000..b0e7698 --- /dev/null +++ b/include/ioapi_buf.h @@ -0,0 +1,52 @@ +/* ioapi_buf.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + This version of ioapi is designed to buffer IO. + + Copyright (C) 2012-2017 Nathan Moinvaziri + https://github.com/nmoinvaz/minizip + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef _IOAPI_BUF_H +#define _IOAPI_BUF_H + +#include +#include +#include + +#include "zlib.h" +#include "ioapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +voidpf ZCALLBACK fopen_buf_func(voidpf opaque, const char* filename, int mode); +voidpf ZCALLBACK fopen64_buf_func(voidpf opaque, const void* filename, int mode); +voidpf ZCALLBACK fopendisk_buf_func(voidpf opaque, voidpf stream_cd, uint32_t number_disk, int mode); +voidpf ZCALLBACK fopendisk64_buf_func(voidpf opaque, voidpf stream_cd, uint32_t number_disk, int mode); +uint32_t ZCALLBACK fread_buf_func(voidpf opaque, voidpf stream, void* buf, uint32_t size); +uint32_t ZCALLBACK fwrite_buf_func(voidpf opaque, voidpf stream, const void* buf, uint32_t size); +long ZCALLBACK ftell_buf_func(voidpf opaque, voidpf stream); +uint64_t ZCALLBACK ftell64_buf_func(voidpf opaque, voidpf stream); +long ZCALLBACK fseek_buf_func(voidpf opaque, voidpf stream, uint32_t offset, int origin); +long ZCALLBACK fseek64_buf_func(voidpf opaque, voidpf stream, uint64_t offset, int origin); +int ZCALLBACK fclose_buf_func(voidpf opaque,voidpf stream); +int ZCALLBACK ferror_buf_func(voidpf opaque,voidpf stream); + +typedef struct ourbuffer_s { + zlib_filefunc_def filefunc; + zlib_filefunc64_def filefunc64; +} ourbuffer_t; + +void fill_buffer_filefunc(zlib_filefunc_def* pzlib_filefunc_def, ourbuffer_t *ourbuf); +void fill_buffer_filefunc64(zlib_filefunc64_def* pzlib_filefunc_def, ourbuffer_t *ourbuf); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/ioapi_mem.h b/include/ioapi_mem.h new file mode 100644 index 0000000..7061d6f --- /dev/null +++ b/include/ioapi_mem.h @@ -0,0 +1,52 @@ +/* ioapi_mem.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + This version of ioapi is designed to access memory rather than files. + We do use a region of memory to put data in to and take it out of. + + Copyright (C) 2012-2017 Nathan Moinvaziri (https://github.com/nmoinvaz/minizip) + (C) 2003 Justin Fletcher + (C) 1998-2003 Gilles Vollant + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef _IOAPI_MEM_H +#define _IOAPI_MEM_H + +#include +#include +#include + +#include "zlib.h" +#include "ioapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +voidpf ZCALLBACK fopen_mem_func(voidpf opaque, const char* filename, int mode); +voidpf ZCALLBACK fopendisk_mem_func(voidpf opaque, voidpf stream, uint32_t number_disk, int mode); +uint32_t ZCALLBACK fread_mem_func(voidpf opaque, voidpf stream, void* buf, uint32_t size); +uint32_t ZCALLBACK fwrite_mem_func(voidpf opaque, voidpf stream, const void* buf, uint32_t size); +long ZCALLBACK ftell_mem_func(voidpf opaque, voidpf stream); +long ZCALLBACK fseek_mem_func(voidpf opaque, voidpf stream, uint32_t offset, int origin); +int ZCALLBACK fclose_mem_func(voidpf opaque, voidpf stream); +int ZCALLBACK ferror_mem_func(voidpf opaque, voidpf stream); + +typedef struct ourmemory_s { + char *base; /* Base of the region of memory we're using */ + uint32_t size; /* Size of the region of memory we're using */ + uint32_t limit; /* Furthest we've written */ + uint32_t cur_offset; /* Current offset in the area */ + int grow; /* Growable memory buffer */ +} ourmemory_t; + +void fill_memory_filefunc(zlib_filefunc_def* pzlib_filefunc_def, ourmemory_t *ourmem); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/ipatch.h b/include/ipatch.h new file mode 100644 index 0000000..27a3c3a --- /dev/null +++ b/include/ipatch.h @@ -0,0 +1,257 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 "globaldefs.h" +#include "debugging/debugging.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 GDEF_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 GDEF_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 GDEF_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 GDEF_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 GDEF_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 GDEF_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; +Vector4 m_color; +}; + +typedef Matrix PatchControlMatrix; + + +class PatchCreator +{ +public: +INTEGER_CONSTANT( Version, 1 ); +STRING_CONSTANT( Name, "patch" ); +virtual scene::Node& createPatch(bool def3, bool ws) = 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.h b/include/iplugin.h new file mode 100644 index 0000000..a67160b --- /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.h b/include/ireference.h new file mode 100644 index 0000000..359f487 --- /dev/null +++ b/include/ireference.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_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.h b/include/irender.h new file mode 100644 index 0000000..b4106fa --- /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/callback.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; +const unsigned int RENDER_POLYOFS = 1 << 23; // glEnable(GL_POLYGON_OFFSET_FILL) +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 Callback RenderableCallback; + +typedef Callback 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 ~OpenGLRenderable() = default; +virtual void render( RenderStateFlags state ) const = 0; +}; + +class Matrix4; +struct qtexture_t; +class ModuleObserver; + +#include "generic/vector.h" + +class Shader +{ +public: +virtual ~Shader() = default; +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.h b/include/iscenegraph.h new file mode 100644 index 0000000..569cf86 --- /dev/null +++ b/include/iscenegraph.h @@ -0,0 +1,215 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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: +virtual ~Graph() = default; +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.h b/include/iscriplib.h new file mode 100644 index 0000000..e070406 --- /dev/null +++ b/include/iscriplib.h @@ -0,0 +1,86 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 ~Tokeniser() = default; +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 ~TokenWriter() = default; +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.h b/include/iselection.h new file mode 100644 index 0000000..9207271 --- /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/callback.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 Callback SelectionChangeCallback; +typedef SignalHandler1 SelectionChangeHandler; + +class SelectionSystem +{ +public: +virtual ~SelectionSystem() = default; +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.h b/include/ishaders.h new file mode 100644 index 0000000..5c2ea84 --- /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/callback.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, + QER_POLYOFS = 1 << 12, +}; + +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 Callback 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; +virtual int getPolygonOffset() 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 Callback 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.h b/include/itexdef.h new file mode 100644 index 0000000..c11953c --- /dev/null +++ b/include/itexdef.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +enum EBrushType { + eBrushTypeQuake, + eBrushTypeQuake2, + eBrushTypeQuake3, + eBrushTypeQuake3BP, + eBrushTypeDoom3, + eBrushTypeQuake4, + eBrushTypeHalfLife, + eBrushTypeQuake3Valve +}; + +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.h b/include/itextstream.h new file mode 100644 index 0000000..7d69c7b --- /dev/null +++ b/include/itextstream.h @@ -0,0 +1,107 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/include/itextures.h new file mode 100644 index 0000000..6fa4b02 --- /dev/null +++ b/include/itextures.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_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: +virtual ~TexturesCache() = default; +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.h b/include/itoolbar.h new file mode 100644 index 0000000..7e5daad --- /dev/null +++ b/include/itoolbar.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_IPLUGTOOLBAR_H ) +#define INCLUDED_IPLUGTOOLBAR_H + +#include +#include "generic/constant.h" + +class IToolbarButton +{ +public: +enum EType +{ + eSpace, + eButton, + eToggleButton, +}; + +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.h b/include/iundo.h new file mode 100644 index 0000000..25fa148 --- /dev/null +++ b/include/iundo.h @@ -0,0 +1,118 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/callback.h" + +class UndoMemento +{ +public: +virtual void release() = 0; +virtual ~UndoMemento() { +} +}; + +class Undoable +{ +public: +virtual UndoMemento* exportState() const = 0; +virtual void importState( const UndoMemento* state ) = 0; +virtual ~Undoable() { +} +}; + +class UndoObserver +{ +public: +virtual void save( Undoable* undoable ) = 0; +virtual ~UndoObserver() { +} +}; + +class UndoTracker +{ +public: +virtual void clear() = 0; +virtual void begin() = 0; +virtual void undo() = 0; +virtual void redo() = 0; +virtual ~UndoTracker() { +} +}; + +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; + +virtual ~UndoSystem() { +} +}; + +#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.h b/include/mapfile.h new file mode 100644 index 0000000..02cd360 --- /dev/null +++ b/include/mapfile.h @@ -0,0 +1,72 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/callback.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.h b/include/modelskin.h new file mode 100644 index 0000000..b070cc7 --- /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/callback.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 Callback SkinRemapCallback; +class ModuleObserver; + +class ModelSkin +{ +public: +virtual ~ModelSkin() = default; +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: +virtual ~ModelSkinCache() = default; +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.h b/include/moduleobserver.h new file mode 100644 index 0000000..0559b85 --- /dev/null +++ b/include/moduleobserver.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_MODULEOBSERVER_H ) +#define INCLUDED_MODULEOBSERVER_H + +class ModuleObserver +{ +public: +virtual ~ModuleObserver() = default; +virtual void unrealise() = 0; +virtual void realise() = 0; +}; + +#endif diff --git a/include/modulesystem.h b/include/modulesystem.h new file mode 100644 index 0000000..f7d4656 --- /dev/null +++ b/include/modulesystem.h @@ -0,0 +1,233 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 "globaldefs.h" +#include "generic/static.h" +#include "debugging/debugging.h" + +#if GDEF_OS_WINDOWS +#define RADIANT_DLLEXPORT __declspec(dllexport) +#define RADIANT_DLLIMPORT __declspec(dllimport) +#else +#define RADIANT_DLLEXPORT __attribute__((visibility("default"))) +#define RADIANT_DLLIMPORT +#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 GDEF_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 GDEF_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.h b/include/nameable.h new file mode 100644 index 0000000..ceb7aab --- /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/callback.h" + +typedef Callback 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.h b/include/namespace.h new file mode 100644 index 0000000..0b24059 --- /dev/null +++ b/include/namespace.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/callback.h" + +typedef Callback NameCallback; +typedef Callback 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 0000000..5796d76 --- /dev/null +++ b/include/preferencesystem.cpp @@ -0,0 +1,187 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 MemberCaller ImportStringCaller; +void exportString( Callback& importer ){ + importer( m_string.c_str() ); +} +typedef MemberCaller&), &StringPreference::exportString> ExportStringCaller; +}; + +inline void int_export( int i, Callback& 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 MemberCaller ImportStringCaller; +void exportString( Callback& importer ){ + int_export( m_int, importer ); +} +typedef MemberCaller&), &IntPreference::exportString> 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 0000000..6479ec9 --- /dev/null +++ b/include/preferencesystem.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/callback.h" +#include "property.h" + +class PreferenceSystem { +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "preferences"); + + virtual void registerPreference(const char *name, const Property &cb) = 0; +}; + +template +Property make_property_string(Self &it) { + return make_property>(it); +} + +template +Property make_property_string(Self &it) { + return make_property_chain, const char *>, I>(it); +} + +template +Property make_property_string() { + return make_property_chain, const char *>, I>(); +} + +#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.h b/include/qerplugin.h new file mode 100644 index 0000000..b6a8865 --- /dev/null +++ b/include/qerplugin.h @@ -0,0 +1,168 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 "uilib/uilib.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 + +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 )( ui::Window parent, const char* text, const char* caption /* = "NetRadiant"*/, 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 )( ui::Window parent, bool open, const char* title, const char* path /* = 0*/, const char* pattern /* = 0*/, bool want_load /* = false*/, bool want_import /* = false*/, bool want_save /* = false*/ ); + +// returns a gchar* string that must be g_free'd by the user +typedef char* ( *PFN_QERAPP_DIRDIALOG )( ui::Window 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 )( ui::Window 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 ui::Image ( *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* ( *getGameFile )( ); + 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); + + 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.h b/include/renderable.h new file mode 100644 index 0000000..d50ca7a --- /dev/null +++ b/include/renderable.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_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 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: +virtual ~Renderable() = default; +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.h b/include/selectable.h new file mode 100644 index 0000000..cb6f04c --- /dev/null +++ b/include/selectable.h @@ -0,0 +1,272 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/callback.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 Callback 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 0000000..21a26ff --- /dev/null +++ b/include/stream_version.h @@ -0,0 +1,2 @@ +// version defines for q3map stream +#define Q3MAP_STREAM_VERSION "1" diff --git a/include/unzip.h b/include/unzip.h new file mode 100644 index 0000000..8889333 --- /dev/null +++ b/include/unzip.h @@ -0,0 +1,308 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.2.0, September 16th, 2017 + part of the MiniZip project + + Copyright (C) 2012-2017 Nathan Moinvaziri + https://github.com/nmoinvaz/minizip + Copyright (C) 2009-2010 Mathias Svensson + Modifications for Zip64 support on both zip and unzip + http://result42.com + Copyright (C) 2007-2008 Even Rouault + Modifications of Unzip for Zip64 + Copyright (C) 1998-2010 Gilles Vollant + http://www.winimage.com/zLibDll/minizip.html + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef _UNZ_H +#define _UNZ_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unz_file__; +typedef unz_file__ *unzFile; +#else +typedef voidp unzFile; +#endif + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) +#define UNZ_BADPASSWORD (-106) + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info64_s +{ + uint64_t number_entry; /* total number of entries in the central dir on this disk */ + uint32_t number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/ + uint16_t size_comment; /* size of the global comment of the zipfile */ +} unz_global_info64; + +typedef struct unz_global_info_s +{ + uint32_t number_entry; /* total number of entries in the central dir on this disk */ + uint32_t number_disk_with_CD; /* number the the disk with central dir, used for spanning ZIP*/ + uint16_t size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info64_s +{ + uint16_t version; /* version made by 2 bytes */ + uint16_t version_needed; /* version needed to extract 2 bytes */ + uint16_t flag; /* general purpose bit flag 2 bytes */ + uint16_t compression_method; /* compression method 2 bytes */ + uint32_t dos_date; /* last mod file date in Dos fmt 4 bytes */ + uint32_t crc; /* crc-32 4 bytes */ + uint64_t compressed_size; /* compressed size 8 bytes */ + uint64_t uncompressed_size; /* uncompressed size 8 bytes */ + uint16_t size_filename; /* filename length 2 bytes */ + uint16_t size_file_extra; /* extra field length 2 bytes */ + uint16_t size_file_comment; /* file comment length 2 bytes */ + + uint32_t disk_num_start; /* disk number start 4 bytes */ + uint16_t internal_fa; /* internal file attributes 2 bytes */ + uint32_t external_fa; /* external file attributes 4 bytes */ + + uint64_t disk_offset; + + uint16_t size_file_extra_internal; +} unz_file_info64; + +typedef struct unz_file_info_s +{ + uint16_t version; /* version made by 2 bytes */ + uint16_t version_needed; /* version needed to extract 2 bytes */ + uint16_t flag; /* general purpose bit flag 2 bytes */ + uint16_t compression_method; /* compression method 2 bytes */ + uint32_t dos_date; /* last mod file date in Dos fmt 4 bytes */ + uint32_t crc; /* crc-32 4 bytes */ + uint32_t compressed_size; /* compressed size 4 bytes */ + uint32_t uncompressed_size; /* uncompressed size 4 bytes */ + uint16_t size_filename; /* filename length 2 bytes */ + uint16_t size_file_extra; /* extra field length 2 bytes */ + uint16_t size_file_comment; /* file comment length 2 bytes */ + + uint16_t disk_num_start; /* disk number start 2 bytes */ + uint16_t internal_fa; /* internal file attributes 2 bytes */ + uint32_t external_fa; /* external file attributes 4 bytes */ + + uint64_t disk_offset; +} unz_file_info; + +/***************************************************************************/ +/* Opening and close a zip file */ + +extern unzFile ZEXPORT unzOpen(const char *path); +extern unzFile ZEXPORT unzOpen64(const void *path); +/* Open a Zip file. + + path should contain the full path (by example, on a Windows XP computer + "c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip". + return NULL if zipfile cannot be opened or doesn't exist + return unzFile handle if no error + + NOTE: The "64" function take a const void *pointer, because the path is just the value passed to the + open64_file_func callback. Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path + is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char *does not describe the reality */ + +extern unzFile ZEXPORT unzOpen2(const char *path, zlib_filefunc_def *pzlib_filefunc_def); +/* Open a Zip file, like unzOpen, but provide a set of file low level API for read/write operations */ +extern unzFile ZEXPORT unzOpen2_64(const void *path, zlib_filefunc64_def *pzlib_filefunc_def); +/* Open a Zip file, like unz64Open, but provide a set of file low level API for read/write 64-bit operations */ + +extern int ZEXPORT unzClose(unzFile file); +/* Close a ZipFile opened with unzOpen. If there is files inside the .Zip opened with unzOpenCurrentFile, + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + + return UNZ_OK if there is no error */ + +extern int ZEXPORT unzGetGlobalInfo(unzFile file, unz_global_info *pglobal_info); +extern int ZEXPORT unzGetGlobalInfo64(unzFile file, unz_global_info64 *pglobal_info); +/* Write info about the ZipFile in the *pglobal_info structure. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzGetGlobalComment(unzFile file, char *comment, uint16_t comment_size); +/* Get the global comment string of the ZipFile, in the comment buffer. + + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 */ + +/***************************************************************************/ +/* Reading the content of the current zipfile, you can open it, read data from it, and close it + (you can close it before reading all the file) */ + +extern int ZEXPORT unzOpenCurrentFile(unzFile file); +/* Open for reading data the current file in the zipfile. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzOpenCurrentFilePassword(unzFile file, const char *password); +/* Open for reading data the current file in the zipfile. + password is a crypting password + + return UNZ_OK if no error */ + +extern int ZEXPORT unzOpenCurrentFile2(unzFile file, int *method, int *level, int raw); +/* Same as unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 *method will receive method of compression, *level will receive level of compression + + NOTE: you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL */ + +extern int ZEXPORT unzOpenCurrentFile3(unzFile file, int *method, int *level, int raw, const char *password); +/* Same as unzOpenCurrentFile, but takes extra parameter password for encrypted files */ + +extern int ZEXPORT unzReadCurrentFile(unzFile file, voidp buf, uint32_t len); +/* Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ + +extern int ZEXPORT unzGetCurrentFileInfo(unzFile file, unz_file_info *pfile_info, char *filename, + uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size); +extern int ZEXPORT unzGetCurrentFileInfo64(unzFile file, unz_file_info64 *pfile_info, char *filename, + uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size); +/* Get Info about the current file + + pfile_info if != NULL, the *pfile_info structure will contain somes info about the current file + filename if != NULL, the file name string will be copied in filename + filename_size is the size of the filename buffer + extrafield if != NULL, the extra field information from the central header will be copied in to + extrafield_size is the size of the extraField buffer + comment if != NULL, the comment string of the file will be copied in to + comment_size is the size of the comment buffer */ + +extern int ZEXPORT unzGetLocalExtrafield(unzFile file, voidp buf, uint32_t len); +/* Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf == NULL, it return the size of the local extra field + if buf != NULL, len is the size of the buffer, the extra header is copied in buf. + + return number of bytes copied in buf, or (if <0) the error code */ + +extern int ZEXPORT unzCloseCurrentFile(unzFile file); +/* Close the file in zip opened with unzOpenCurrentFile + + return UNZ_CRCERROR if all the file was read but the CRC is not good */ + +/***************************************************************************/ +/* Browse the directory of the zipfile */ + +typedef int (*unzFileNameComparer)(unzFile file, const char *filename1, const char *filename2); +typedef int (*unzIteratorFunction)(unzFile file); +typedef int (*unzIteratorFunction2)(unzFile file, unz_file_info64 *pfile_info, char *filename, + uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size); + +extern int ZEXPORT unzGoToFirstFile(unzFile file); +/* Set the current file of the zipfile to the first file. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzGoToFirstFile2(unzFile file, unz_file_info64 *pfile_info, char *filename, + uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size); +/* Set the current file of the zipfile to the first file and retrieves the current info on success. + Not as seek intensive as unzGoToFirstFile + unzGetCurrentFileInfo. + + return UNZ_OK if no error */ + +extern int ZEXPORT unzGoToNextFile(unzFile file); +/* Set the current file of the zipfile to the next file. + + return UNZ_OK if no error + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */ + +extern int ZEXPORT unzGoToNextFile2(unzFile file, unz_file_info64 *pfile_info, char *filename, + uint16_t filename_size, void *extrafield, uint16_t extrafield_size, char *comment, uint16_t comment_size); +/* Set the current file of the zipfile to the next file and retrieves the current + info on success. Does less seeking around than unzGotoNextFile + unzGetCurrentFileInfo. + + return UNZ_OK if no error + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest */ + +extern int ZEXPORT unzLocateFile(unzFile file, const char *filename, unzFileNameComparer filename_compare_func); +/* Try locate the file szFileName in the zipfile. For custom filename comparison pass in comparison function. + + return UNZ_OK if the file is found (it becomes the current file) + return UNZ_END_OF_LIST_OF_FILE if the file is not found */ + +/***************************************************************************/ +/* Raw access to zip file */ + +typedef struct unz_file_pos_s +{ + uint32_t pos_in_zip_directory; /* offset in zip file directory */ + uint32_t num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos(unzFile file, unz_file_pos *file_pos); +extern int ZEXPORT unzGoToFilePos(unzFile file, unz_file_pos *file_pos); + +typedef struct unz64_file_pos_s +{ + uint64_t pos_in_zip_directory; /* offset in zip file directory */ + uint64_t num_of_file; /* # of file */ +} unz64_file_pos; + +extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos *file_pos); +extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos *file_pos); + +extern int32_t ZEXPORT unzGetOffset(unzFile file); +extern int64_t ZEXPORT unzGetOffset64(unzFile file); +/* Get the current file offset */ + +extern int ZEXPORT unzSetOffset(unzFile file, uint32_t pos); +extern int ZEXPORT unzSetOffset64(unzFile file, uint64_t pos); +/* Set the current file offset */ + +extern int32_t ZEXPORT unzTell(unzFile file); +extern int64_t ZEXPORT unzTell64(unzFile file); +/* return current position in uncompressed data */ + +extern int ZEXPORT unzSeek(unzFile file, uint32_t offset, int origin); +extern int ZEXPORT unzSeek64(unzFile file, uint64_t offset, int origin); +/* Seek within the uncompressed data if compression method is storage */ + +extern int ZEXPORT unzEndOfFile(unzFile file); +/* return 1 if the end of file was reached, 0 elsewhere */ + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* _UNZ_H */ diff --git a/include/version.h b/include/version.h new file mode 100644 index 0000000..b1322a8 --- /dev/null +++ b/include/version.h @@ -0,0 +1,13 @@ +// Makefile appends preprocessor flags instead now +#ifndef WorldSpawn_VERSION +#error no WorldSpawn_VERSION defined +#endif +#ifndef WorldSpawn_MAJOR_VERSION +#error no WorldSpawn_MAJOR_VERSION defined +#endif +#ifndef WorldSpawn_MINOR_VERSION +#error no WorldSpawn_MINOR_VERSION defined +#endif +#ifndef WorldSpawn_PATCH_VERSION +#error no WorldSpawn_PATCH_VERSION defined +#endif diff --git a/include/warnings.h b/include/warnings.h new file mode 100644 index 0000000..ebbd4f8 --- /dev/null +++ b/include/warnings.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_WARNINGS_H ) +#define INCLUDED_WARNINGS_H + +#include "globaldefs.h" + +#if GDEF_COMPILER_MSVC && _MSC_VER > 1000 +#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.h b/include/windowobserver.h new file mode 100644 index 0000000..2494863 --- /dev/null +++ b/include/windowobserver.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_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 ~WindowObserver() = default; +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/include/zip.h b/include/zip.h new file mode 100644 index 0000000..c90806f --- /dev/null +++ b/include/zip.h @@ -0,0 +1,212 @@ +/* zip.h -- IO on .zip files using zlib + Version 1.2.0, September 16th, 2017 + part of the MiniZip project + + Copyright (C) 2012-2017 Nathan Moinvaziri + https://github.com/nmoinvaz/minizip + Copyright (C) 2009-2010 Mathias Svensson + Modifications for Zip64 support + http://result42.com + Copyright (C) 1998-2010 Gilles Vollant + http://www.winimage.com/zLibDll/minizip.html + + This program is distributed under the terms of the same license as zlib. + See the accompanying LICENSE file for the full text of the license. +*/ + +#ifndef _ZIP_H +#define _ZIP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +# include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +# include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +# include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagzipFile__ { int unused; } zip_file__; +typedef zip_file__ *zipFile; +#else +typedef voidp zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#ifndef DEF_MEM_LEVEL +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif + +typedef struct +{ + uint32_t dos_date; + uint16_t internal_fa; /* internal file attributes 2 bytes */ + uint32_t external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +/***************************************************************************/ +/* Writing a zip file */ + +extern zipFile ZEXPORT zipOpen(const char *path, int append); +extern zipFile ZEXPORT zipOpen64(const void *path, int append); +/* Create a zipfile. + + path should contain the full path (by example, on a Windows XP computer + "c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip". + + return NULL if zipfile cannot be opened + return zipFile handle if no error + + If the file path exist and append == APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. (useful if the file contain a self extractor code) + If the file path exist and append == APPEND_STATUS_ADDINZIP, we will add files in existing + zip (be sure you don't add file that doesn't exist) + + NOTE: There is no delete function into a zipfile. If you want delete file into a zipfile, + you must open a zipfile, and create another. Of course, you can use RAW reading and writing to copy + the file you did not want delete. */ + +extern zipFile ZEXPORT zipOpen2(const char *path, int append, const char **globalcomment, + zlib_filefunc_def *pzlib_filefunc_def); + +extern zipFile ZEXPORT zipOpen2_64(const void *path, int append, const char **globalcomment, + zlib_filefunc64_def *pzlib_filefunc_def); + +extern zipFile ZEXPORT zipOpen3(const char *path, int append, uint64_t disk_size, + const char **globalcomment, zlib_filefunc_def *pzlib_filefunc_def); +/* Same as zipOpen2 but allows specification of spanned zip size */ + +extern zipFile ZEXPORT zipOpen3_64(const void *path, int append, uint64_t disk_size, + const char **globalcomment, zlib_filefunc64_def *pzlib_filefunc_def); + +extern int ZEXPORT zipOpenNewFileInZip(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level); +/* Open a file in the ZIP for writing. + + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + extrafield_local buffer to store the local header extra field data, can be NULL + size_extrafield_local size of extrafield_local buffer + extrafield_global buffer to store the global header extra field data, can be NULL + size_extrafield_global size of extrafield_local buffer + comment buffer for comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) + zip64 is set to 1 if a zip64 extended information block should be added to the local file header. + this MUST be '1' if the uncompressed size is >= 0xffffffff. */ + +extern int ZEXPORT zipOpenNewFileInZip64(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level, int zip64); +/* Same as zipOpenNewFileInZip with zip64 support */ + +extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level, int raw); +/* Same as zipOpenNewFileInZip, except if raw=1, we write raw file */ + +extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level, int raw, int zip64); +/* Same as zipOpenNewFileInZip3 with zip64 support */ + +extern int ZEXPORT zipOpenNewFileInZip3(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level, int raw, int windowBits, int memLevel, + int strategy, const char *password, ZIP_UNUSED uint32_t crc_for_crypting); +/* Same as zipOpenNewFileInZip2, except + windowBits, memLevel, strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crc_for_crypting : crc of file to compress (needed for crypting) */ + +extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level, int raw, int windowBits, int memLevel, + int strategy, const char *password, ZIP_UNUSED uint32_t crc_for_crypting, int zip64); +/* Same as zipOpenNewFileInZip3 with zip64 support */ + +extern int ZEXPORT zipOpenNewFileInZip4(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level, int raw, int windowBits, int memLevel, + int strategy, const char *password, ZIP_UNUSED uint32_t crc_for_crypting, uint16_t version_madeby, uint16_t flag_base); +/* Same as zipOpenNewFileInZip3 except versionMadeBy & flag fields */ + +extern int ZEXPORT zipOpenNewFileInZip4_64(zipFile file, const char *filename, const zip_fileinfo *zipfi, + const void *extrafield_local, uint16_t size_extrafield_local, const void *extrafield_global, + uint16_t size_extrafield_global, const char *comment, uint16_t method, int level, int raw, int windowBits, int memLevel, + int strategy, const char *password, ZIP_UNUSED uint32_t crc_for_crypting, uint16_t version_madeby, uint16_t flag_base, int zip64); +/* Same as zipOpenNewFileInZip4 with zip64 support */ + +extern int ZEXPORT zipOpenNewFileInZip5(zipFile file, + const char *filename, + const zip_fileinfo *zipfi, + const void *extrafield_local, + uint16_t size_extrafield_local, + const void *extrafield_global, + uint16_t size_extrafield_global, + const char *comment, + uint16_t flag_base, + int zip64, + uint16_t method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char *password, + int aes); +/* Allowing optional aes */ + +extern int ZEXPORT zipWriteInFileInZip(zipFile file, const void *buf, uint32_t len); +/* Write data in the zipfile */ + +extern int ZEXPORT zipCloseFileInZip(zipFile file); +/* Close the current file in the zipfile */ + +extern int ZEXPORT zipCloseFileInZipRaw(zipFile file, uint32_t uncompressed_size, uint32_t crc32); +extern int ZEXPORT zipCloseFileInZipRaw64(zipFile file, uint64_t uncompressed_size, uint32_t crc32); +/* Close the current file in the zipfile, for file opened with parameter raw=1 in zipOpenNewFileInZip2 + where raw is compressed data. Parameters uncompressed_size and crc32 are value for the uncompressed data. */ + +extern int ZEXPORT zipClose(zipFile file, const char *global_comment); +/* Close the zipfile */ + +extern int ZEXPORT zipClose_64(zipFile file, const char *global_comment); + +extern int ZEXPORT zipClose2_64(zipFile file, const char *global_comment, uint16_t version_madeby); +/* Same as zipClose_64 except version_madeby field */ + +/***************************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* _ZIP_H */ diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt new file mode 100644 index 0000000..9f5f834 --- /dev/null +++ b/libs/CMakeLists.txt @@ -0,0 +1,64 @@ +add_subdirectory(cmdlib) +add_subdirectory(container) +add_subdirectory(ddslib) +add_subdirectory(debugging) +add_subdirectory(etclib) +add_subdirectory(filematch) +add_subdirectory(generic) +if (BUILD_RADIANT) + add_subdirectory(gtkutil) +endif () +add_subdirectory(l_net) +add_subdirectory(math) +add_subdirectory(mathlib) +add_subdirectory(memory) +add_subdirectory(modulesystem) +add_subdirectory(os) +add_subdirectory(picomodel) +add_subdirectory(profile) +add_subdirectory(script) +add_subdirectory(signal) +add_subdirectory(splines) +add_subdirectory(stream) +add_subdirectory(string) +add_subdirectory(uilib) +add_subdirectory(xml) + +add_library(libs + _.cpp + archivelib.h + bytebool.h + bytestreamutils.h + character.h + convert.h + dragplanes.h + eclasslib.h + entitylib.h + entityxml.h + fs_filesystem.h + fs_path.h + globaldefs.h + imagelib.h + property.h + instancelib.h + maplib.h + moduleobservers.h + pivot.h + render.h + scenelib.h + selectionlib.h + shaderlib.h + str.h + stringio.h + texturelib.h + transformlib.h + traverselib.h + typesystem.h + undolib.h + uniquenames.h + versionlib.h + ) + +find_package(GLIB REQUIRED) +target_include_directories(libs PRIVATE ${GLIB_INCLUDE_DIRS}) +target_link_libraries(libs PRIVATE ${GLIB_LIBRARIES}) diff --git a/libs/_.cpp b/libs/_.cpp new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/libs/_.cpp @@ -0,0 +1 @@ + diff --git a/libs/archivelib.h b/libs/archivelib.h new file mode 100644 index 0000000..78fc7eb --- /dev/null +++ b/libs/archivelib.h @@ -0,0 +1,224 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/bytebool.h new file mode 100644 index 0000000..7e3d330 --- /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.h b/libs/bytestreamutils.h new file mode 100644 index 0000000..69ec375 --- /dev/null +++ b/libs/bytestreamutils.h @@ -0,0 +1,159 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +#include "globaldefs.h" + +#if GDEF_COMPILER_GNU + +#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 (GDEF_ARCH_ENDIAN_BIG) { + std::reverse(reinterpret_cast( &value ), + reinterpret_cast( &value ) + sizeof(Type)); + } +} + +template +inline void istream_read_big_endian( InputStreamType& istream, Type& value ){ + istream.read(reinterpret_cast( &value ), sizeof(Type)); + if (!GDEF_ARCH_ENDIAN_BIG) { + std::reverse(reinterpret_cast( &value ), + reinterpret_cast( &value ) + sizeof(Type)); + } +} + +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 int16_t istream_read_int16_be( InputStreamType& istream ){ + int16_t value; + istream_read_big_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 uint16_t istream_read_uint16_be( InputStreamType& istream ){ + uint16_t value; + istream_read_big_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 int32_t istream_read_int32_be( InputStreamType& istream ){ + int32_t value; + istream_read_big_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 uint32_t istream_read_uint32_be( InputStreamType& istream ){ + uint32_t value; + istream_read_big_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 float istream_read_float32_be( InputStreamType& istream ){ + float value; + istream_read_big_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.h b/libs/character.h new file mode 100644 index 0000000..bc35d3f --- /dev/null +++ b/libs/character.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_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 0000000..abdc15a --- /dev/null +++ b/libs/cmdlib.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 "globaldefs.h" +#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, bool waitfor ); + +// 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 +#if GDEF_OS_WINDOWS +#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/CMakeLists.txt b/libs/cmdlib/CMakeLists.txt new file mode 100644 index 0000000..5d25ce8 --- /dev/null +++ b/libs/cmdlib/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(cmdlib + cmdlib.cpp ../cmdlib.h + ) diff --git a/libs/cmdlib/cmdlib.cpp b/libs/cmdlib/cmdlib.cpp new file mode 100644 index 0000000..9bfd243 --- /dev/null +++ b/libs/cmdlib/cmdlib.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 + */ + +// +// start of shared cmdlib stuff +// + +#include "cmdlib.h" +#include "globaldefs.h" + +#include +#include + +#include "string/string.h" +#include "os/path.h" +#include "container/array.h" + + +#if GDEF_OS_POSIX + +#include +#include +#include + +bool Q_Exec( const char *cmd, char *cmdline, const char *, bool, bool waitfor ){ + char fullcmd[2048]; + char *pCmd; + pid_t pid; +#if GDEF_DEBUG + printf( "Q_Exec damnit\n" ); +#endif + switch ( ( pid = fork() ) ) + { + default: + if ( waitfor ) { + waitpid( pid, NULL, 0 ); + } + break; + 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++; +#if GDEF_DEBUG + printf( "Running system...\n" ); + printf( "Command: %s\n", pCmd ); +#endif + system( pCmd ); +#if GDEF_DEBUG + printf( "system() returned\n" ); +#endif + _exit( 0 ); + break; + } + return true; +} + +#elif GDEF_OS_WINDOWS + +#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, bool waitfor ){ + 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 + ) ) { + if ( waitfor ) { + WaitForSingleObject( ProcessInformation.hProcess, INFINITE ); + } + return true; + } + return false; +} + +#endif diff --git a/libs/container/CMakeLists.txt b/libs/container/CMakeLists.txt new file mode 100644 index 0000000..00bfbb4 --- /dev/null +++ b/libs/container/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(container + array.cpp array.h + cache.h + container.h + hashfunc.h + hashtable.cpp hashtable.h + stack.h + ) diff --git a/libs/container/array.cpp b/libs/container/array.cpp new file mode 100644 index 0000000..8ce77f5 --- /dev/null +++ b/libs/container/array.cpp @@ -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 + */ + +#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 0000000..5de4fe8 --- /dev/null +++ b/libs/container/array.h @@ -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 + */ + +#if !defined( INCLUDED_CONTAINER_ARRAY_H ) +#define INCLUDED_CONTAINER_ARRAY_H + +#include "globaldefs.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 GDEF_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 GDEF_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.h b/libs/container/cache.h new file mode 100644 index 0000000..047e4ea --- /dev/null +++ b/libs/container/cache.h @@ -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 + */ + +#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.h b/libs/container/container.h new file mode 100644 index 0000000..a2d1fec --- /dev/null +++ b/libs/container/container.h @@ -0,0 +1,330 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +#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.h b/libs/container/hashfunc.h new file mode 100644 index 0000000..ef27e8b --- /dev/null +++ b/libs/container/hashfunc.h @@ -0,0 +1,402 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..f2e6602 --- /dev/null +++ b/libs/container/hashtable.cpp @@ -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 + */ + +#include "hashtable.h" +#include "globaldefs.h" + +#if GDEF_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 0000000..7fa7907 --- /dev/null +++ b/libs/container/hashtable.h @@ -0,0 +1,410 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#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.h b/libs/container/stack.h new file mode 100644 index 0000000..10040a7 --- /dev/null +++ b/libs/container/stack.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_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.h b/libs/convert.h new file mode 100644 index 0000000..dbbc499 --- /dev/null +++ b/libs/convert.h @@ -0,0 +1,266 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 "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; + gsize inbytesleft = 1; + char* outbuf = m_converted[i]; + gsize 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 0000000..4961f6e --- /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/CMakeLists.txt b/libs/ddslib/CMakeLists.txt new file mode 100644 index 0000000..d4de1a7 --- /dev/null +++ b/libs/ddslib/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(ddslib + ddslib.c ../ddslib.h + ) diff --git a/libs/ddslib/ddslib.c b/libs/ddslib/ddslib.c new file mode 100644 index 0000000..15dbef8 --- /dev/null +++ b/libs/ddslib/ddslib.c @@ -0,0 +1,757 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* dependencies */ +#include "ddslib.h" +#include "globaldefs.h" + +/* endian tomfoolery */ +typedef union +{ + float f; + char c[ 4 ]; +} +floatSwapUnion; + +#if GDEF_ARCH_ENDIAN_BIG + +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*) ( (char *) 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*) ( (char *) 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*) ( (char *) 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/debugging/CMakeLists.txt b/libs/debugging/CMakeLists.txt new file mode 100644 index 0000000..e5880de --- /dev/null +++ b/libs/debugging/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(debugging + debugging.cpp debugging.h + ) diff --git a/libs/debugging/debugging.cpp b/libs/debugging/debugging.cpp new file mode 100644 index 0000000..b292840 --- /dev/null +++ b/libs/debugging/debugging.cpp @@ -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 + */ + +#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 0000000..405131d --- /dev/null +++ b/libs/debugging/debugging.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_DEBUGGING_DEBUGGING_H ) +#define INCLUDED_DEBUGGING_DEBUGGING_H + +/// \file +/// \brief Debugging macros for fatal error/assert messages. + +#include "globaldefs.h" +#include "stream/textstream.h" +#include "warnings.h" +#include "generic/static.h" + +#if GDEF_COMPILER_MSVC && ( defined( _M_IX86 ) || defined( _M_AMD64 ) ) +#define DEBUGGER_BREAKPOINT() __asm { int 3 } +#elif GDEF_COMPILER_GNU && __GNUC__ >= 2 && ( defined ( __i386__ ) || defined ( __x86_64__ ) ) +#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__ ) + +#define DEBUG_ASSERTS + +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 GDEF_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" ) +#define ASSERT_TRUE( flag ) ASSERT_MESSAGE( !!(flag) == true, "condition \"" #flag "\" is false" ) + +#else + +#define ASSERT_MESSAGE( condition, message ) +#define ERROR_MESSAGE( message ) +#define ASSERT_NOTNULL( ptr ) + +#endif + +#endif diff --git a/libs/dragplanes.h b/libs/dragplanes.h new file mode 100644 index 0000000..e85a5cd --- /dev/null +++ b/libs/dragplanes.h @@ -0,0 +1,228 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/eclasslib.h new file mode 100644 index 0000000..2a92fcd --- /dev/null +++ b/libs/eclasslib.h @@ -0,0 +1,306 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +{ +using ListItem = std::pair; +using ListItems = std::vector; +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.h b/libs/entitylib.h new file mode 100644 index 0000000..09ba84d --- /dev/null +++ b/libs/entitylib.h @@ -0,0 +1,686 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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_forward, const Vector3& direction_left, const Vector3& direction_up ){ + Vector3 endpoint( vector3_added( origin, vector3_scaled( direction_forward, 32.0 ) ) ); + + Vector3 tip1( vector3_added( vector3_added( endpoint, vector3_scaled( direction_forward, -8.0 ) ), vector3_scaled( direction_up, -4.0 ) ) ); + Vector3 tip2( vector3_added( tip1, vector3_scaled( direction_up, 8.0 ) ) ); + Vector3 tip3( vector3_added( vector3_added( endpoint, vector3_scaled( direction_forward, -8.0 ) ), vector3_scaled( direction_left, -4.0 ) ) ); + Vector3 tip4( vector3_added( tip3, vector3_scaled( direction_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] ){ + unsigned int indices[26] = { + 0, 1, 1, 2, 2, 3, 3, 0, + 4, 5, 5, 6, 6, 7, 7, 4, + 0, 4, 1, 5, 2, 6, 3, 7, + // 0, 6, 1, 7, 2, 4, 3, 5 // X cross + 1, 7 // diagonal line (connect mins to maxs corner) + }; +#if 1 + glVertexPointer( 3, GL_FLOAT, 0, points ); + glDrawElements( GL_LINES, sizeof( indices ) / sizeof( indices[0] ), GL_UNSIGNED_INT, indices ); +#else + glBegin( GL_LINES ); + for ( std::size_t i = 0; i < sizeof( indices ) / sizeof( indices[0] ); ++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 MemberCaller 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 MemberCaller 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 ); + } + int getKeyEntries( void ) const { + int i = 0; + + for ( KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i ) + { + i++; + } + } + + 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.h b/libs/entityxml.h new file mode 100644 index 0000000..89a9406 --- /dev/null +++ b/libs/entityxml.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_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/etclib.c b/libs/etclib.c new file mode 100644 index 0000000..09a149e --- /dev/null +++ b/libs/etclib.c @@ -0,0 +1,114 @@ +// Copyright 2009 Google Inc. +// +// Based on the code from Android ETC1Util. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "etclib.h" + +static void ETC_DecodeETC1SubBlock( byte *out, qboolean outRGBA, int r, int g, int b, int tableIndex, unsigned int low, qboolean second, qboolean flipped ){ + int baseX = 0, baseY = 0; + const int modifierTable[] = { + 2, 8, -2, -8, + 5, 17, -5, -17, + 9, 29, -9, -29, + 13, 42, -13, -42, + 18, 60, -18, -60, + 24, 80, -24, -80, + 33, 106, -33, -106, + 47, 183, -47, -183 + }; + const int *table = modifierTable + tableIndex * 4; + int i; + + if ( second ) { + if ( flipped ) { + baseY = 2; + } + else { + baseX = 2; + } + } + + for ( i = 0; i < 8; i++ ) + { + int x, y, k, delta; + int qr, qg, qb; + byte *q; + + if ( flipped ) { + x = baseX + ( i >> 1 ); + y = baseY + ( i & 1 ); + } + else { + x = baseX + ( i >> 2 ); + y = baseY + ( i & 3 ); + } + k = y + ( x * 4 ); + delta = table[( ( low >> k ) & 1 ) | ( ( low >> ( k + 15 ) ) & 2 )]; + + qr = r + delta; + qg = g + delta; + qb = b + delta; + if ( outRGBA ) { + q = out + 4 * ( x + 4 * y ); + } + else { + q = out + 3 * ( x + 4 * y ); + } + *( q++ ) = ( ( qr > 0 ) ? ( ( qr < 255 ) ? qr : 255 ) : 0 ); + *( q++ ) = ( ( qg > 0 ) ? ( ( qg < 255 ) ? qg : 255 ) : 0 ); + *( q++ ) = ( ( qb > 0 ) ? ( ( qb < 255 ) ? qb : 255 ) : 0 ); + if ( outRGBA ) { + *( q++ ) = 255; + } + } +} + +void ETC_DecodeETC1Block( const byte* in, byte* out, qboolean outRGBA ){ + unsigned int high = ( in[0] << 24 ) | ( in[1] << 16 ) | ( in[2] << 8 ) | in[3]; + unsigned int low = ( in[4] << 24 ) | ( in[5] << 16 ) | ( in[6] << 8 ) | in[7]; + int r1, r2, g1, g2, b1, b2; + qboolean flipped = ( ( high & 1 ) != 0 ); + + if ( high & 2 ) { + int rBase, gBase, bBase; + const int lookup[] = { 0, 1, 2, 3, -4, -3, -2, -1 }; + + rBase = ( high >> 27 ) & 31; + r1 = ( rBase << 3 ) | ( rBase >> 2 ); + rBase = ( rBase + ( lookup[( high >> 24 ) & 7] ) ) & 31; + r2 = ( rBase << 3 ) | ( rBase >> 2 ); + + gBase = ( high >> 19 ) & 31; + g1 = ( gBase << 3 ) | ( gBase >> 2 ); + gBase = ( gBase + ( lookup[( high >> 16 ) & 7] ) ) & 31; + g2 = ( gBase << 3 ) | ( gBase >> 2 ); + + bBase = ( high >> 11 ) & 31; + b1 = ( bBase << 3 ) | ( bBase >> 2 ); + bBase = ( bBase + ( lookup[( high >> 8 ) & 7] ) ) & 31; + b2 = ( bBase << 3 ) | ( bBase >> 2 ); + } + else { + r1 = ( ( high >> 24 ) & 0xf0 ) | ( ( high >> 28 ) & 0xf ); + r2 = ( ( high >> 20 ) & 0xf0 ) | ( ( high >> 24 ) & 0xf ); + g1 = ( ( high >> 16 ) & 0xf0 ) | ( ( high >> 20 ) & 0xf ); + g2 = ( ( high >> 12 ) & 0xf0 ) | ( ( high >> 16 ) & 0xf ); + b1 = ( ( high >> 8 ) & 0xf0 ) | ( ( high >> 12 ) & 0xf ); + b2 = ( ( high >> 4 ) & 0xf0 ) | ( ( high >> 8 ) & 0xf ); + } + + ETC_DecodeETC1SubBlock( out, outRGBA, r1, g1, b1, ( high >> 5 ) & 7, low, qfalse, flipped ); + ETC_DecodeETC1SubBlock( out, outRGBA, r2, g2, b2, ( high >> 2 ) & 7, low, qtrue, flipped ); +} diff --git a/libs/etclib.h b/libs/etclib.h new file mode 100644 index 0000000..7d24074 --- /dev/null +++ b/libs/etclib.h @@ -0,0 +1,33 @@ +// Copyright 2009 Google Inc. +// +// Based on the code from Android ETC1Util. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INCLUDED_ETCLIB_H +#define INCLUDED_ETCLIB_H + +#include "bytebool.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +void ETC_DecodeETC1Block( const byte* in, byte* out, qboolean outRGBA ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/etclib/CMakeLists.txt b/libs/etclib/CMakeLists.txt new file mode 100644 index 0000000..8d8fb23 --- /dev/null +++ b/libs/etclib/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(etclib + ../etclib.c ../etclib.h + ) diff --git a/libs/filematch.c b/libs/filematch.c new file mode 100644 index 0000000..5b01db7 --- /dev/null +++ b/libs/filematch.c @@ -0,0 +1,74 @@ +#include +#include "filematch.h" + +// LordHavoc: some portable directory listing code I wrote for lmp2pcx, now used in darkplaces to load id1/*.pak and such... + +int matchpattern( const char *in, const char *pattern, int caseinsensitive ){ + return matchpattern_with_separator( in, pattern, caseinsensitive, "/\\:", 0 ); +} + +// wildcard_least_one: if true * matches 1 or more characters +// if false * matches 0 or more characters +int matchpattern_with_separator( const char *in, const char *pattern, int caseinsensitive, const char *separators, int wildcard_least_one ){ + int c1, c2; + while ( *pattern ) + { + switch ( *pattern ) + { + case 0: + return 1; // end of pattern + case '?': // match any single character + if ( *in == 0 || strchr( separators, *in ) ) { + return 0; // no match + } + in++; + pattern++; + break; + case '*': // match anything until following string + if ( wildcard_least_one ) { + if ( *in == 0 || strchr( separators, *in ) ) { + return 0; // no match + } + in++; + } + pattern++; + while ( *in ) + { + if ( strchr( separators, *in ) ) { + break; + } + // see if pattern matches at this offset + if ( matchpattern_with_separator( in, pattern, caseinsensitive, separators, wildcard_least_one ) ) { + return 1; + } + // nope, advance to next offset + in++; + } + break; + default: + if ( *in != *pattern ) { + if ( !caseinsensitive ) { + return 0; // no match + } + c1 = *in; + if ( c1 >= 'A' && c1 <= 'Z' ) { + c1 += 'a' - 'A'; + } + c2 = *pattern; + if ( c2 >= 'A' && c2 <= 'Z' ) { + c2 += 'a' - 'A'; + } + if ( c1 != c2 ) { + return 0; // no match + } + } + in++; + pattern++; + break; + } + } + if ( *in ) { + return 0; // reached end of pattern but not end of input + } + return 1; // success +} diff --git a/libs/filematch.h b/libs/filematch.h new file mode 100644 index 0000000..cdb340a --- /dev/null +++ b/libs/filematch.h @@ -0,0 +1,16 @@ +#if !defined( INCLUDED_FILEMATCH_H ) +#define INCLUDED_FILEMATCH_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +int matchpattern( const char *in, const char *pattern, int caseinsensitive ); +int matchpattern_with_separator( const char *in, const char *pattern, int caseinsensitive, const char *separators, int wildcard_least_one ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/filematch/CMakeLists.txt b/libs/filematch/CMakeLists.txt new file mode 100644 index 0000000..c7d8a9e --- /dev/null +++ b/libs/filematch/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(filematch + ../filematch.c ../filematch.h + ) diff --git a/libs/fs_filesystem.h b/libs/fs_filesystem.h new file mode 100644 index 0000000..5cd42a6 --- /dev/null +++ b/libs/fs_filesystem.h @@ -0,0 +1,160 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/fs_path.h new file mode 100644 index 0000000..2baf297 --- /dev/null +++ b/libs/fs_path.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/CMakeLists.txt b/libs/generic/CMakeLists.txt new file mode 100644 index 0000000..d89f574 --- /dev/null +++ b/libs/generic/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(generic + arrayrange.h + bitfield.h + callback.cpp callback.h + constant.cpp constant.h + enumeration.h + functional.h + object.cpp object.h + reference.h + referencecounted.h + static.cpp static.h + vector.h + ) diff --git a/libs/generic/arrayrange.h b/libs/generic/arrayrange.h new file mode 100644 index 0000000..e036cf3 --- /dev/null +++ b/libs/generic/arrayrange.h @@ -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 + */ + +#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.h b/libs/generic/bitfield.h new file mode 100644 index 0000000..f9427aa --- /dev/null +++ b/libs/generic/bitfield.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_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 0000000..7022e8c --- /dev/null +++ b/libs/generic/callback.cpp @@ -0,0 +1,255 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" +#include "globaldefs.h" + +#if GDEF_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 Member Test1; +typedef MemberCaller Test1Caller; +void test1const( A1 ) const { +} +typedef ConstMember Test1Const; +typedef ConstMemberCaller Test1ConstCaller; +void test2( A1, A2 ){ +} +typedef Member Test2; +void test2const( A1, A2 ) const { +} +typedef ConstMember Test2Const; +void test3( A1, A2, A3 ){ +} +typedef Member Test3; +void test3const( A1, A2, A3 ) const { +} +typedef ConstMember Test3Const; +}; + +void test0free(){ +} +void test1free( A1 ){ +} +void test2free( A1, A2 ){ +} +typedef Function Test2Free; +void test3free( A1, A2, A3 ){ +} +typedef Function 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 ReferenceCaller Test1Caller; + +void test1const( const Test& test, A1 ){ +} +typedef ConstReferenceCaller Test1ConstCaller; + +void test1p( Test* test, A1 ){ +} +typedef PointerCaller Test1PCaller; + +void test1constp( const Test* test, A1 ){ +} +typedef ConstPointerCaller Test1ConstPCaller; + +void test2( Test& test, A1, A2 ){ +} +typedef Function Test2; + +void test3( Test& test, A1, A2, A3 ){ +} +typedef Function Test3; + +void instantiate(){ + Test test; + const Test& testconst = test; + { + Callback a = makeCallbackF(&test0free); + Callback b = Test::Test0Caller( test ); + b = makeCallback( Test::Test0(), test ); + Callback c = Test::Test0ConstCaller( testconst ); + c = makeCallback( Test::Test0Const(), test ); + Test0Caller{ test }; + Test0ConstCaller{ testconst }; + Test0PCaller{ &test }; + Test0ConstPCaller{ &testconst }; + a(); + bool u = a != b; + } + { + typedef Callback TestCallback1; + TestCallback1 a = makeCallbackF(&test1free); + TestCallback1 b = Test::Test1Caller( test ); + b = makeCallback( Test::Test1(), test ); + TestCallback1 c = Test::Test1ConstCaller( testconst ); + c = makeCallback( Test::Test1Const(), test ); + Test1Caller{ test }; + Test1ConstCaller{ testconst }; + Test1PCaller{ &test }; + Test1ConstPCaller{ &testconst }; + a( A1() ); + bool u = a != b; + } + { + typedef Callback TestCallback2; + TestCallback2 a = makeStatelessCallback( Test2Free() ); + TestCallback2 b = makeCallback( Test2(), test ); + makeCallback( Test::Test2(), test ); + makeCallback( Test::Test2Const(), test ); + a( A1(), A2() ); + bool u = a != b; + } + { + typedef Callback TestCallback3; + TestCallback3 a = makeStatelessCallback( Test3Free() ); + TestCallback3 b = makeCallback( Test3(), test ); + makeCallback( Test::Test3(), test ); + makeCallback( 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 0000000..577dd9d --- /dev/null +++ b/libs/generic/callback.h @@ -0,0 +1,349 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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" + +namespace detail { + + 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()); + } + +} + +namespace detail { + + template + struct ConvertFromOpaque { + }; + + // reference + + template + inline const void *convertToOpaque(const T &t) { + return &t; + } + + template + struct ConvertFromOpaque { + static T const &apply(void *p) { + return *static_cast(p); + } + }; + + template + inline void *convertToOpaque(T &t) { + return &t; + } + + template + struct ConvertFromOpaque { + static T &apply(void *p) { + return *static_cast( p ); + } + }; + + // pointer + + template + inline const void *convertToOpaque(const T *t) { + return t; + } + + template + struct ConvertFromOpaque { + static const T *apply(void *p) { + return static_cast(p); + } + }; + + template + inline void *convertToOpaque(T *t) { + return t; + } + + template + struct ConvertFromOpaque { + static T *apply(void *p) { + return static_cast(p); + } + }; + + // function pointer + + template + inline const void *convertToOpaque(R(*const &t)(Ts...)) { + return &t; + } + + template + struct ConvertFromOpaque { + using Type = R(*)(Ts...); + + static Type const &apply(void *p) { + return *static_cast(p); + } + }; + + template + inline void *convertToOpaque(R(*&t)(Ts...)) { + return &t; + } + + template + struct ConvertFromOpaque { + using Type = R(*)(Ts...); + + static Type &apply(void *p) { + return *static_cast(p); + } + }; + + template + class BindFirstOpaqueN; + + template + class BindFirstOpaqueN { + FirstBound firstBound; + public: + explicit BindFirstOpaqueN(FirstBound firstBound) : firstBound(firstBound) { + } + + R operator()(Ts... args) const { + return Caller::call(firstBound, args...); + } + + FirstBound getBound() const { + return firstBound; + } + + static R thunk(void *environment, Ts... args) { + return thunk_(detail::ConvertFromOpaque::apply(environment), args...); + } + + static R thunk_(FirstBound environment, Ts... args) { + return Caller::call(environment, args...); + } + + void *getEnvironment() const { + return const_cast(detail::convertToOpaque(firstBound)); + } + }; + +} + +template +using BindFirstOpaque = detail::BindFirstOpaqueN>; + +/// \brief Combines a void pointer with a pointer to a function which operates on a void pointer. +/// +/// Use with the callback constructors MemberCaller0, ConstMemberCaller0, ReferenceCaller0, ConstReferenceCaller0, PointerCaller0, ConstPointerCaller0 and FreeCaller0. +template +class Callback; + +template +class Callback : public detail::CallbackBase { + using Base = detail::CallbackBase; + + static R nullThunk(void *, Ts...) { + } + +public: + using func = R(Ts...); + + Callback() : Base(0, nullThunk) { + } + + template + Callback(const BindFirstOpaque &caller) : Base(caller.getEnvironment(), BindFirstOpaque::thunk) { + } + + Callback(void *environment, typename Base::Thunk function) : Base(environment, function) { + } + + R operator()(Ts... args) const { + return Base::getThunk()(Base::getEnvironment(), args...); + } +}; + +namespace detail { + template + struct Arglist; + + template + struct Arglist { + using type = R(Head, Ts...); + + template + using unshift = Arglist; + + using shift = Arglist; + }; + + template + struct Arglist { + using type = R(Ts...); + + template + using unshift = Arglist; + }; + + template + using ArgShift = typename detail::Arglist::shift::type; + + template + using ArgUnshift = typename detail::Arglist::template unshift::type; +} + +template +inline Callback>> makeCallback(const Caller &caller, get_argument callee) { + return BindFirstOpaque(callee); +} + +template +class CallerShiftFirst; + +template +class CallerShiftFirst { +public: + using func = R(FirstArgument, Ts...); + + static R call(FirstArgument, Ts... args) { + return Caller::call(args...); + } +}; + +template +inline Callback> makeStatelessCallback(const Caller &caller) { + return makeCallback(CallerShiftFirst, void *>>(), nullptr); +} + +/// \brief Forms a Callback from a non-const Environment reference and a non-const Environment member-function. +template member> +using MemberCaller = BindFirstOpaque>; + +/// \brief Constructs a Callback1 from a non-const \p functor +/// +/// \param Functor Must define \c first_argument_type and \c operator()(first_argument_type). +template +inline Callback> makeCallback(Functor &functor) { + return MemberCaller, &Functor::operator()>(functor); +} + +/// \brief Forms a Callback from a const Environment reference and a const Environment member-function. +template member> +using ConstMemberCaller = BindFirstOpaque>; + +/// \brief Constructs a Callback1 from a const \p functor +/// +/// \param Functor Must define \c first_argument_type and const \c operator()(first_argument_type). +template +inline Callback> makeCallback(const Functor &functor) { + return ConstMemberCaller, &Functor::operator()>(functor); +} + +/// \brief Forms a Callback from a non-const Environment reference and a free function which operates on a non-const Environment reference. +template *func> +using ReferenceCaller = BindFirstOpaque, func>>; + +/// \brief Forms a Callback from a const Environment reference and a free function which operates on a const Environment reference. +template *func> +using ConstReferenceCaller = BindFirstOpaque, func>>; + +/// \brief Forms a Callback from a non-const Environment pointer and a free function which operates on a non-const Environment pointer. +template *func> +using PointerCaller = BindFirstOpaque, func>>; + +/// \brief Forms a Callback from a const Environment pointer and a free function which operates on a const Environment pointer. +template *func> +using ConstPointerCaller = BindFirstOpaque, func>>; + +namespace detail { + template + class FreeCaller : public BindFirstOpaque>> { + public: + FreeCaller() : BindFirstOpaque>>(nullptr) { + } + }; + + template + struct FreeCallerWrapper; + + template + struct FreeCallerWrapper { + using func = R(void *, Ts...); + + static R call(void *f, Ts... args) { + // ideally, we'd get the implementation of the function type directly. Instead, it's passed in + return reinterpret_cast(f)(args...); + } + }; +} + +/// \brief Forms a Callback from a free function +template +using FreeCaller = detail::FreeCaller, F>; + +template +inline Callback makeCallbackF(R(*func)(Ts...)) { + void *pVoid = reinterpret_cast(func); + return BindFirstOpaque>(pVoid); +} + +#endif diff --git a/libs/generic/constant.cpp b/libs/generic/constant.cpp new file mode 100644 index 0000000..bcf9b4e --- /dev/null +++ b/libs/generic/constant.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 "constant.h" +#include "globaldefs.h" + +#if GDEF_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 0000000..e7e7ba0 --- /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 ) +#define UINT_CONSTANT( name, value ) TYPE_CONSTANT ( name, value, unsigned int ) + +STRING_CONSTANT( EmptyString, "" ); + +#endif diff --git a/libs/generic/enumeration.h b/libs/generic/enumeration.h new file mode 100644 index 0000000..8d6c75e --- /dev/null +++ b/libs/generic/enumeration.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_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.h b/libs/generic/functional.h new file mode 100644 index 0000000..2b4fe1b --- /dev/null +++ b/libs/generic/functional.h @@ -0,0 +1,210 @@ +#if !defined( INCLUDED_FUNCTIONAL_H ) +#define INCLUDED_FUNCTIONAL_H + +#include +#include + +namespace detail { + + template + struct rank : rank { + }; + + template<> + struct rank<0> { + }; + + struct get_func { + + template + struct wrapper { + using type = T; + }; + + template + using func_member = wrapper; + + template + static wrapper> test(rank<2>) { return {}; } + + template + struct func_lambda { + using type = typename func_lambda::type; + }; + + template + struct func_lambda { + using type = R(Ts...); + }; + + template + struct func_lambda { + using type = R(Ts...); + }; + + template + struct func_lambda { + using type = R(Ts...); + }; + + template> + static wrapper> test(rank<1>) { return {}; } + }; + + template + struct Fn; + + template + struct Fn { + using result_type = R; + + template + using get = typename std::tuple_element>::type; + }; +} + +template +using get_func = typename decltype(detail::get_func::test(detail::rank<2>{}))::type::type; + +template +using get_result_type = typename detail::Fn>::result_type; + +template +using get_argument = typename detail::Fn>::template get; + +namespace detail { + + template + class FunctionN; + + template + class FunctionN { + public: + template + class instance { + public: + using func = R(Ts...); + + static R call(Ts... args) { + return (f)(args...); + } + }; + }; + +} + +template +using Function = typename detail::FunctionN::template instance; + +namespace detail { + template + struct MemberFunction; + + template + struct MemberFunction { + using type = R(Object::*)(Ts...); + using type_const = R(Object::*)(Ts...) const; + }; +} + +namespace detail { + template + class MemberN; + + template + class MemberN { + public: + template + class instance { + public: + using func = R(Object &, Ts...); + + static R call(Object &object, Ts... args) { + return (object.*f)(args...); + } + }; + }; +} + +template +using MemberFunction = typename detail::MemberFunction::type; + +template func> +using Member = typename detail::MemberN::template instance; + +namespace detail { + template + class ConstMemberN; + + template + class ConstMemberN { + public: + template + class instance { + public: + using func = R(const Object &, Ts...); + + static R call(const Object &object, Ts... args) { + return (object.*f)(args...); + } + }; + }; +} + +template +using ConstMemberFunction = typename detail::MemberFunction::type_const; + +template func> +using ConstMember = typename detail::ConstMemberN::template instance; + +// misc + +namespace detail { + template + struct seq { + }; + + template + struct gens : gens { + }; + + template + struct gens<0, S...> { + using type = seq; + }; + + template + using seq_new = typename gens::type; + + template + class FunctorNInvoke; + + template + class FunctorNInvoke { + std::tuple args; + + template + struct caller; + + template + struct caller> { + static inline R call(FunctorNInvoke *self, Functor functor) { + (void) self; + return functor(std::get(self->args)...); + } + }; + + public: + FunctorNInvoke(Ts... args) : args(args...) { + } + + inline R operator()(Functor functor) { + return caller>::call(this, functor); + } + }; +} + +template +using FunctorInvoke = detail::FunctorNInvoke>; + +#endif diff --git a/libs/generic/object.cpp b/libs/generic/object.cpp new file mode 100644 index 0000000..bbb5610 --- /dev/null +++ b/libs/generic/object.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 "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 0000000..084f760 --- /dev/null +++ b/libs/generic/object.h @@ -0,0 +1,90 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +#include "globaldefs.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 GDEF_COMPILER_MSVC && _MSC_VER > 1000 +#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.h b/libs/generic/reference.h new file mode 100644 index 0000000..df5a05a --- /dev/null +++ b/libs/generic/reference.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_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.h b/libs/generic/referencecounted.h new file mode 100644 index 0000000..37abd91 --- /dev/null +++ b/libs/generic/referencecounted.h @@ -0,0 +1,180 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..a92246b --- /dev/null +++ b/libs/generic/static.cpp @@ -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 + */ + +#include "static.h" +#include "globaldefs.h" + +#if GDEF_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 0000000..ad501fc --- /dev/null +++ b/libs/generic/static.h @@ -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 + */ + +#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.h b/libs/generic/vector.h new file mode 100644 index 0000000..ea1e3bd --- /dev/null +++ b/libs/generic/vector.h @@ -0,0 +1,212 @@ + +#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/globaldefs.h b/libs/globaldefs.h new file mode 100644 index 0000000..146dd78 --- /dev/null +++ b/libs/globaldefs.h @@ -0,0 +1,125 @@ +#ifndef INCLUDED_LIBS_GLOBALDEFS +#define INCLUDED_LIBS_GLOBALDEFS + +// ARCH_ENDIAN + +#if defined(__BIG_ENDIAN__) || defined(_SGI_SOURCE) +#define GDEF_ARCH_ENDIAN_BIG 1 +#else +#define GDEF_ARCH_ENDIAN_BIG 0 +#endif + +// ARCH_BITS + +#if defined(__i386__) || defined(_M_IX86) +#define GDEF_ARCH_BITS_32 1 +#else +#define GDEF_ARCH_BITS_32 0 +#endif + +#if defined(__LP64__) || defined(_M_X64) || defined(_M_AMD64) || defined(_WIN64) +#define GDEF_ARCH_BITS_64 1 +#else +#define GDEF_ARCH_BITS_64 0 +#endif + +// OS + +#if defined(POSIX) +#define GDEF_OS_POSIX 1 +#else +#define GDEF_OS_POSIX 0 +#endif + +#if defined(WIN32) || defined(_WIN32) || defined(_WIN64) +#define GDEF_OS_WINDOWS 1 +#else +#define GDEF_OS_WINDOWS 0 +#endif + +#if defined(__APPLE__) +#define GDEF_OS_MACOS 1 +#else +#define GDEF_OS_MACOS 0 +#endif + +#if defined(__linux__) +#define GDEF_OS_LINUX 1 +#else +#define GDEF_OS_LINUX 0 +#endif + + +#define GDEF_OS_BSD 0 + +#if defined(__FreeBSD__) +#undef GDEF_OS_BSD +#define GDEF_OS_BSD 1 +#define GDEF_OS_BSD_FREE 1 +#else +#define GDEF_OS_BSD_FREE 0 +#endif + +#if defined(__NetBSD__) +#undef GDEF_OS_BSD +#define GDEF_OS_BSD 1 +#define GDEF_OS_BSD_NET 1 +#else +#define GDEF_OS_BSD_NET 0 +#endif + +#if defined(__OpenBSD__) +#undef GDEF_OS_BSD +#define GDEF_OS_BSD 1 +#define GDEF_OS_BSD_OPEN 1 +#else +#define GDEF_OS_BSD_OPEN 0 +#endif + +#if defined(__DragonFly__) +#undef GDEF_OS_BSD +#define GDEF_OS_BSD 1 +#define GDEF_OS_BSD_DRAGONFLY 1 +#else +#define GDEF_OS_BSD_DRAGONFLY 0 +#endif + +// COMPILER + +#if defined(_MSC_VER) +#define GDEF_COMPILER_MSVC 1 +#else +#define GDEF_COMPILER_MSVC 0 +#endif + +#if defined(__GNUC__) +#define GDEF_COMPILER_GNU 1 +#else +#define GDEF_COMPILER_GNU 0 +#endif + +// ATTRIBUTE + +#if GDEF_COMPILER_GNU +#define GDEF_ATTRIBUTE_NORETURN __attribute__((noreturn)) +#else +#define GDEF_ATTRIBUTE_NORETURN +#endif + +#ifdef GDEF_COMPILER_MSVC +#define GDEF_ATTRIBUTE_INLINE __inline +#else +#define GDEF_ATTRIBUTE_INLINE inline +#endif + +// MISC + +#define GDEF_DEBUG 0 +#if defined(_DEBUG) +#if _DEBUG +#undef GDEF_DEBUG +#define GDEF_DEBUG 1 +#endif +#endif + +#endif diff --git a/libs/gtkutil/CMakeLists.txt b/libs/gtkutil/CMakeLists.txt new file mode 100644 index 0000000..b62098c --- /dev/null +++ b/libs/gtkutil/CMakeLists.txt @@ -0,0 +1,37 @@ +add_library(gtkutil + accelerator.cpp accelerator.h + button.cpp button.h + clipboard.cpp clipboard.h + closure.h + container.h + cursor.cpp cursor.h + dialog.cpp dialog.h + entry.cpp entry.h + filechooser.cpp filechooser.h + frame.cpp frame.h + glfont.cpp glfont.h + glwidget.cpp glwidget.h + idledraw.h + image.cpp image.h + menu.cpp menu.h + messagebox.cpp messagebox.h + nonmodal.cpp nonmodal.h + paned.cpp paned.h + pointer.h + toolbar.cpp toolbar.h + widget.cpp widget.h + window.cpp window.h + xorrectangle.cpp xorrectangle.h + ) + +target_include_directories(gtkutil PRIVATE uilib) +target_link_libraries(gtkutil PRIVATE uilib) + +target_include_directories(gtkutil PRIVATE ${GTK${GTK_TARGET}_INCLUDE_DIRS}) +target_link_libraries(gtkutil PRIVATE ${GTK${GTK_TARGET}_LIBRARIES}) + +if (GTK_TARGET EQUAL 2) + find_package(GtkGLExt REQUIRED) + target_include_directories(gtkutil PRIVATE ${GtkGLExt_INCLUDE_DIRS}) + target_link_libraries(gtkutil PRIVATE ${GtkGLExt_LIBRARIES}) +endif () diff --git a/libs/gtkutil/accelerator.cpp b/libs/gtkutil/accelerator.cpp new file mode 100644 index 0000000..30d9f66 --- /dev/null +++ b/libs/gtkutil/accelerator.cpp @@ -0,0 +1,609 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "generic/callback.h" +#include "generic/bitfield.h" +#include "string/string.h" + +#include "pointer.h" +#include "closure.h" + +#include + + +const char *global_keys_find(unsigned int key) +{ + const char *s; + if (key == 0) { + return ""; + } + s = gdk_keyval_name(key); + if (!s) { + return ""; + } + return s; +} + +unsigned int global_keys_find(const char *name) +{ + guint k; + if (!name || !*name) { + return 0; + } + k = gdk_keyval_from_name(name); + if (k == GDK_KEY_VoidSymbol) { + return 0; + } + return k; +} + +void accelerator_write(const Accelerator &accelerator, TextOutputStream &ostream) +{ +#if 0 + 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 ); + } +#endif + ostream << gtk_accelerator_get_label(accelerator.key, accelerator.modifiers); +} + +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_KEY_ISO_Left_Tab) { + keyval = GDK_KEY_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(ui::Window 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(ui::Window 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(ui::AccelGroup group, Accelerator accelerator, const Callback &callback); + +void accel_group_remove_accelerator(ui::AccelGroup 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(); +} + +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) { + ui::Window toplevel = *i; + ASSERT_MESSAGE(window_has_accel(toplevel), "ERROR"); + ASSERT_MESSAGE(gtk_widget_is_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 + } + } + 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) { + ui::Window toplevel = *i; + ASSERT_MESSAGE(!window_has_accel(toplevel), "ERROR"); + ASSERT_MESSAGE(gtk_widget_is_toplevel(toplevel), "enabling accel for non-toplevel window"); + toplevel.add_accel_group(global_accel); +#if 0 + globalOutputStream() << reinterpret_cast( toplevel ) << ": enabled global accelerators\n"; +#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(ui::Widget 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(ui::Widget 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(ui::Widget widget, GdkEventFocus *event, PressedButtons *pressed) +{ + Buttons_releaseAll(pressed->buttons); + return FALSE; +} + +void PressedButtons_connect(PressedButtons &pressedButtons, ui::Widget widget) +{ + widget.connect("button_press_event", G_CALLBACK(PressedButtons_button_press), &pressedButtons); + widget.connect("button_release_event", G_CALLBACK(PressedButtons_button_release), &pressedButtons); + widget.connect("focus_out_event", G_CALLBACK(PressedButtons_focus_out), &pressedButtons); +} + +PressedButtons g_pressedButtons; + + +#include +#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(ui::Widget widget, GdkEventKey *event, PressedKeys *pressedKeys) +{ + //globalOutputStream() << "pressed: " << event->keyval << "\n"; + return event->state == 0 && Keys_press(pressedKeys->keys, event->keyval); +} + +gboolean PressedKeys_key_release(ui::Widget widget, GdkEventKey *event, PressedKeys *pressedKeys) +{ + //globalOutputStream() << "released: " << event->keyval << "\n"; + return Keys_release(pressedKeys->keys, event->keyval); +} + +gboolean PressedKeys_focus_in(ui::Widget widget, GdkEventFocus *event, PressedKeys *pressedKeys) +{ + ++pressedKeys->refcount; + return FALSE; +} + +gboolean PressedKeys_focus_out(ui::Widget 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(ui::Window window) +{ + unsigned int key_press_handler = window.connect("key_press_event", G_CALLBACK(PressedKeys_key_press), + &g_pressedKeys); + unsigned int key_release_handler = window.connect("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 = window.connect("focus_in_event", G_CALLBACK(PressedKeys_focus_in), &g_pressedKeys); + unsigned int focus_out_handler = window.connect("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(ui::Window 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(ui::AccelGroup group, ui::Widget widget, guint key, GdkModifierType modifiers, gpointer data) +{ + (*reinterpret_cast *>( data ))(); + return TRUE; +} + +GClosure *accel_group_add_accelerator(ui::AccelGroup 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(ui::AccelGroup 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); + } +} + +ui::AccelGroup global_accel{ui::New}; + +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(ui::Window window, GdkEventKey *event, gpointer data) +{ + gboolean b = gtk_window_propagate_key_event(window, event); + return b; +} + +void global_accel_connect_window(ui::Window window) +{ +#if 1 + unsigned int override_handler = window.connect("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 = window.connect("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 = window.connect( "key_press_event", G_CALLBACK( accelerator_key_event ), &g_keydown_accelerators ); + unsigned int key_release_handler = window.connect( "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); + window.add_accel_group(global_accel); +} + +void global_accel_disconnect_window(ui::Window 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 0000000..7228603 --- /dev/null +++ b/libs/gtkutil/accelerator.h @@ -0,0 +1,164 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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" +#include "property.h" + +// ignore numlock +#define ALLOWED_MODIFIERS ( ~( GDK_MOD2_MASK | GDK_LOCK_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK ) ) + +struct Accelerator { + Accelerator(guint _key) + : key(gdk_keyval_to_upper(_key)), modifiers((GdkModifierType) 0) + { + } + + Accelerator(guint _key, GdkModifierType _modifiers) + : key(gdk_keyval_to_upper(_key)), modifiers((GdkModifierType) (_modifiers & ALLOWED_MODIFIERS)) + { + } + + Accelerator(const Accelerator &src) + : key(gdk_keyval_to_upper(src.key)), modifiers((GdkModifierType) (src.modifiers & ALLOWED_MODIFIERS)) + { + } + + bool operator<(const Accelerator &other) const + { + guint k1 = key; + guint k2 = other.key; + int mod1 = modifiers & ALLOWED_MODIFIERS; + int mod2 = other.modifiers & ALLOWED_MODIFIERS; + return k1 < k2 || (!(k2 < k1) && mod1 < mod2); + } + + bool operator==(const Accelerator &other) const + { + guint k1 = key; + guint k2 = other.key; + int mod1 = modifiers & ALLOWED_MODIFIERS; + int mod2 = other.modifiers & ALLOWED_MODIFIERS; + return k1 == k2 && mod1 == mod2; + } + + Accelerator &operator=(const Accelerator &other) + { + key = other.key; + modifiers = (GdkModifierType) (other.modifiers & ALLOWED_MODIFIERS); + return *this; + } + + 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); + +void global_accel_connect_window(ui::Window window); + +void global_accel_disconnect_window(ui::Window window); + +void GlobalPressedKeys_releaseAll(); + +extern ui::AccelGroup global_accel; + +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; + Callback &)> m_exportCallback; + + Toggle(const Callback &callback, const Accelerator &accelerator, + const Callback &)> &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; + +void PressedButtons_connect(PressedButtons &pressedButtons, ui::Widget widget); + +extern PressedButtons g_pressedButtons; + +#endif diff --git a/libs/gtkutil/button.cpp b/libs/gtkutil/button.cpp new file mode 100644 index 0000000..2a74c4b --- /dev/null +++ b/libs/gtkutil/button.cpp @@ -0,0 +1,160 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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(ui::Widget widget, gpointer data) +{ + (*reinterpret_cast *>( data ))(); +} + +void button_connect_callback(ui::Button 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 +} + +void button_connect_callback(ui::ToolButton 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(ui::ToggleButton 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; +} + +guint toggle_button_connect_callback(ui::ToggleToolButton 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(ui::Button button, const char *icon) +{ + ui::Image image = ui::Image(new_local_image(icon)); + image.show(); + button.add(image); +} + +void toggle_button_set_active_no_signal(ui::ToggleButton 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 toggle_button_set_active_no_signal(ui::ToggleToolButton button, gboolean active) +{ + guint handler_id = gpointer_to_int(g_object_get_data(G_OBJECT(button), "handler")); + g_signal_handler_block(G_OBJECT(button), handler_id); + gtk_toggle_tool_button_set_active(button, active); + g_signal_handler_unblock(G_OBJECT(button), handler_id); +} + + +void radio_button_print_state(ui::RadioButton button) +{ + globalOutputStream() << "toggle button: "; + for (GSList *radio = gtk_radio_button_get_group(button); radio != 0; radio = g_slist_next(radio)) { + globalOutputStream() << gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio->data)); + } + globalOutputStream() << "\n"; +} + +ui::ToggleButton radio_button_get_nth(ui::RadioButton radio, int index) +{ + GSList *group = gtk_radio_button_get_group(radio); + return ui::ToggleButton::from(g_slist_nth_data(group, g_slist_length(group) - index - 1)); +} + +void radio_button_set_active(ui::RadioButton 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(ui::RadioButton 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(ui::RadioButton radio) +{ + //radio_button_print_state(radio); + GSList *group = gtk_radio_button_get_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 0000000..908f6a0 --- /dev/null +++ b/libs/gtkutil/button.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "generic/callback.h" + +typedef int gint; +typedef gint gboolean; +typedef unsigned int guint; + +void button_connect_callback(ui::Button button, const Callback &callback); + +void button_connect_callback(ui::ToolButton button, const Callback &callback); + +guint toggle_button_connect_callback(ui::ToggleButton button, const Callback &callback); + +guint toggle_button_connect_callback(ui::ToggleToolButton button, const Callback &callback); + +void button_set_icon(ui::Button button, const char *icon); + +void toggle_button_set_active_no_signal(ui::ToggleButton item, gboolean active); + +void toggle_button_set_active_no_signal(ui::ToggleToolButton item, gboolean active); + +void radio_button_set_active(ui::RadioButton radio, int index); + +void radio_button_set_active_no_signal(ui::RadioButton radio, int index); + +int radio_button_get_active(ui::RadioButton radio); + +#endif diff --git a/libs/gtkutil/clipboard.cpp b/libs/gtkutil/clipboard.cpp new file mode 100644 index 0000000..26c3592 --- /dev/null +++ b/libs/gtkutil/clipboard.cpp @@ -0,0 +1,155 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.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 GDEF_OS_WINDOWS + +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 char RADIANT_CLIPPINGS_STR[] = "RADIANT_CLIPPINGS"; + +static const GtkTargetEntry clipboard_targets[] = { + {RADIANT_CLIPPINGS_STR, 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 (gtk_selection_data_get_length(data) < 0) { + globalErrorStream() << "Error retrieving selection\n"; + } else if (strcmp(gdk_atom_name(gtk_selection_data_get_data_type(data)), clipboard_targets[0].target) == 0) { + BufferInputStream istream(reinterpret_cast( gtk_selection_data_get_data(data)), + gtk_selection_data_get_length(data)); + (*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 0000000..ab8d48c --- /dev/null +++ b/libs/gtkutil/clipboard.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_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.h b/libs/gtkutil/closure.h new file mode 100644 index 0000000..3993d39 --- /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.h b/libs/gtkutil/container.h new file mode 100644 index 0000000..12d0ca9 --- /dev/null +++ b/libs/gtkutil/container.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_GTKUTIL_CONTAINER_H ) +#define INCLUDED_GTKUTIL_CONTAINER_H + +inline void container_remove_all(ui::Container container) +{ + container.foreach([=](ui::Widget it) mutable { + container.remove(it); + }); +} + +#endif diff --git a/libs/gtkutil/cursor.cpp b/libs/gtkutil/cursor.cpp new file mode 100644 index 0000000..6717757 --- /dev/null +++ b/libs/gtkutil/cursor.cpp @@ -0,0 +1,131 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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() +{ + return gdk_cursor_new(GDK_BLANK_CURSOR); +} + +void blank_cursor(ui::Widget widget) +{ + GdkCursor *cursor = create_blank_cursor(); + gdk_window_set_cursor(gtk_widget_get_window(widget), cursor); + gdk_cursor_unref(cursor); +} + +void default_cursor(ui::Widget widget) +{ + gdk_window_set_cursor(gtk_widget_get_window(widget), 0); +} + + +void Sys_GetCursorPos(ui::Window window, int *x, int *y) +{ + gdk_display_get_pointer(gdk_display_get_default(), 0, x, y, 0); +} + +void Sys_SetCursorPos(ui::Window window, int x, int y) +{ + GdkScreen *screen; + gdk_display_get_pointer(gdk_display_get_default(), &screen, 0, 0, 0); + gdk_display_warp_pointer(gdk_display_get_default(), screen, x, y); +} + +gboolean DeferredMotion::gtk_motion(ui::Widget widget, GdkEventMotion *event, DeferredMotion *self) +{ + self->motion(event->x, event->y, event->state); + return FALSE; +} + +gboolean FreezePointer::motion_delta(ui::Window widget, GdkEventMotion *event, FreezePointer *self) +{ + int current_x, current_y; + Sys_GetCursorPos(widget, ¤t_x, ¤t_y); + int dx = current_x - self->last_x; + int dy = current_y - self->last_y; + int ddx = current_x - self->recorded_x; + int ddy = current_y - self->recorded_y; + self->last_x = current_x; + self->last_y = current_y; + if (dx != 0 || dy != 0) { + //globalOutputStream() << "motion x: " << dx << ", y: " << dy << "\n"; + if (ddx < -32 || ddx > 32 || ddy < -32 || ddy > 32) { + Sys_SetCursorPos(widget, self->recorded_x, self->recorded_y); + self->last_x = self->recorded_x; + self->last_y = self->recorded_y; + } + self->m_function(dx, dy, event->state, self->m_data); + } + return FALSE; +} + +void FreezePointer::freeze_pointer(ui::Window window, FreezePointer::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_get_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); + + last_x = recorded_x; + last_y = recorded_y; + + m_function = function; + m_data = data; + + handle_motion = window.connect("motion_notify_event", G_CALLBACK(motion_delta), this); +} + +void FreezePointer::unfreeze_pointer(ui::Window 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); +} diff --git a/libs/gtkutil/cursor.h b/libs/gtkutil/cursor.h new file mode 100644 index 0000000..20e3840 --- /dev/null +++ b/libs/gtkutil/cursor.h @@ -0,0 +1,146 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 "debugging/debugging.h" + +typedef struct _GdkCursor GdkCursor; +typedef struct _GdkEventMotion GdkEventMotion; + +GdkCursor *create_blank_cursor(); + +void blank_cursor(ui::Widget widget); + +void default_cursor(ui::Widget widget); + +void Sys_GetCursorPos(ui::Window window, int *x, int *y); + +void Sys_SetCursorPos(ui::Window 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(ui::Widget widget, GdkEventMotion *event, DeferredMotion *self); +}; + +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, last_x, last_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(ui::Window widget, GdkEventMotion *event, FreezePointer *self); + + void freeze_pointer(ui::Window window, MotionDeltaFunction function, void *data); + + void unfreeze_pointer(ui::Window window); +}; + +#endif diff --git a/libs/gtkutil/dialog.cpp b/libs/gtkutil/dialog.cpp new file mode 100644 index 0000000..9024e28 --- /dev/null +++ b/libs/gtkutil/dialog.cpp @@ -0,0 +1,293 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "button.h" +#include "window.h" + +ui::VBox create_dialog_vbox(int spacing, int border) +{ + auto vbox = ui::VBox(FALSE, spacing); + vbox.show(); + gtk_container_set_border_width(GTK_CONTAINER(vbox), border); + return vbox; +} + +ui::HBox create_dialog_hbox(int spacing, int border) +{ + auto hbox = ui::HBox(FALSE, spacing); + hbox.show(); + gtk_container_set_border_width(GTK_CONTAINER(hbox), border); + return hbox; +} + +ui::Frame create_dialog_frame(const char *label, ui::Shadow shadow) +{ + auto frame = ui::Frame(label); + frame.show(); + gtk_frame_set_shadow_type(frame, (GtkShadowType) shadow); + return frame; +} + +ui::Table +create_dialog_table(unsigned int rows, unsigned int columns, unsigned int row_spacing, unsigned int col_spacing, + int border) +{ + auto table = ui::Table(rows, columns, FALSE); + table.show(); + 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; +} + +ui::Button create_dialog_button(const char *label, GCallback func, gpointer data) +{ + auto button = ui::Button(label); + button.dimensions(64, -1); + button.show(); + button.connect("clicked", func, data); + return button; +} + +ui::Window +create_dialog_window(ui::Window parent, const char *title, GCallback func, gpointer data, int default_w, int default_h) +{ + ui::Window 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); + window.connect("delete_event", func, data); + + return window; +} + +gboolean modal_dialog_button_clicked(ui::Widget widget, ModalDialogButton *button) +{ + button->m_dialog.loop = false; + button->m_dialog.ret = button->m_value; + return TRUE; +} + +gboolean modal_dialog_delete(ui::Widget widget, GdkEvent *event, ModalDialog *dialog) +{ + dialog->loop = 0; + dialog->ret = eIDCANCEL; + return TRUE; +} + +EMessageBoxReturn modal_dialog_show(ui::Window window, ModalDialog &dialog) +{ + gtk_grab_add(window); + window.show(); + + dialog.loop = true; + while (dialog.loop) { + gtk_main_iteration(); + } + + window.hide(); + gtk_grab_remove(window); + + return dialog.ret; +} + +ui::Button create_modal_dialog_button(const char *label, ModalDialogButton &button) +{ + return create_dialog_button(label, G_CALLBACK(modal_dialog_button_clicked), &button); +} + +ui::Window +create_modal_dialog_window(ui::Window 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); +} + +ui::Window +create_fixedsize_modal_dialog_window(ui::Window parent, const char *title, ModalDialog &dialog, int width, int height) +{ + auto 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); + + //window.dimensions(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, window, &geometry, (GdkWindowHints)(GDK_HINT_POS|GDK_HINT_MIN_SIZE|GDK_HINT_BASE_SIZE)); + + return window; +} + +gboolean dialog_button_ok(ui::Widget widget, ModalDialog *data) +{ + data->loop = false; + data->ret = eIDOK; + return TRUE; +} + +gboolean dialog_button_cancel(ui::Widget widget, ModalDialog *data) +{ + data->loop = false; + data->ret = eIDCANCEL; + return TRUE; +} + +gboolean dialog_button_yes(ui::Widget widget, ModalDialog *data) +{ + data->loop = false; + data->ret = eIDYES; + return TRUE; +} + +gboolean dialog_button_no(ui::Widget widget, ModalDialog *data) +{ + data->loop = false; + data->ret = eIDNO; + return TRUE; +} + +gboolean dialog_delete_callback(ui::Widget widget, GdkEventAny *event, ModalDialog *data) +{ + widget.hide(); + data->loop = false; + return TRUE; +} + +ui::Window create_simple_modal_dialog_window(const char *title, ModalDialog &dialog, ui::Widget contents) +{ + ui::Window window = create_fixedsize_modal_dialog_window(ui::Window{ui::null}, title, dialog); + + auto vbox1 = create_dialog_vbox(8, 4); + window.add(vbox1); + + vbox1.add(contents); + + ui::Alignment alignment = ui::Alignment(0.5, 0.0, 0.0, 0.0); + alignment.show(); + vbox1.pack_start(alignment, FALSE, FALSE, 0); + + auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + alignment.add(button); + + return window; +} + +RadioHBox RadioHBox_new(StringArrayRange names) +{ + auto hbox = ui::HBox(TRUE, 4); + hbox.show(); + + GSList *group = 0; + auto radio = ui::RadioButton(ui::null); + for (StringArrayRange::Iterator i = names.first; i != names.last; ++i) { + radio = ui::RadioButton::from(gtk_radio_button_new_with_label(group, *i)); + radio.show(); + hbox.pack_start(radio, FALSE, FALSE, 0); + + group = gtk_radio_button_get_group(radio); + } + + return RadioHBox(hbox, radio); +} + + +PathEntry PathEntry_new() +{ + auto frame = ui::Frame(); + frame.show(); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + + // path entry + auto hbox = ui::HBox(FALSE, 0); + hbox.show(); + + auto entry = ui::Entry(ui::New); + gtk_entry_set_has_frame(entry, FALSE); + entry.show(); + hbox.pack_start(entry, TRUE, TRUE, 0); + + // browse button + auto button = ui::Button(ui::New); + button_set_icon(button, "ellipsis.bmp"); + button.show(); + hbox.pack_end(button, FALSE, FALSE, 0); + + frame.add(hbox); + + return PathEntry(frame, entry, button); +} + +void PathEntry_setPath(PathEntry &self, const char *path) +{ + gtk_entry_set_text(self.m_entry, path); +} + +typedef ReferenceCaller PathEntrySetPathCaller; + +void BrowsedPathEntry_clicked(ui::Widget widget, BrowsedPathEntry *self) +{ + self->m_browse(PathEntrySetPathCaller(self->m_entry)); +} + +BrowsedPathEntry::BrowsedPathEntry(const BrowseCallback &browse) : + m_entry(PathEntry_new()), + m_browse(browse) +{ + m_entry.m_button.connect("clicked", G_CALLBACK(BrowsedPathEntry_clicked), this); +} + + +ui::Label DialogLabel_new(const char *name) +{ + auto label = ui::Label(name); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + + return label; +} + +ui::Table DialogRow_new(const char *name, ui::Widget widget) +{ + auto table = ui::Table(1, 3, TRUE); + table.show(); + + gtk_table_set_col_spacings(table, 4); + gtk_table_set_row_spacings(table, 0); + + table.attach(DialogLabel_new(name), {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + + table.attach(widget, {1, 3, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + + return table; +} + +void DialogVBox_packRow(ui::Box vbox, ui::Widget row) +{ + vbox.pack_start(row, FALSE, FALSE, 0); +} diff --git a/libs/gtkutil/dialog.h b/libs/gtkutil/dialog.h new file mode 100644 index 0000000..b359bc1 --- /dev/null +++ b/libs/gtkutil/dialog.h @@ -0,0 +1,148 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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" + +typedef int gint; +typedef gint gboolean; +typedef struct _GdkEventAny GdkEventAny; + + +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; + +ui::Window create_fixedsize_modal_window(ui::Window parent, const char *title, int width, int height); + +ui::Window create_dialog_window(ui::Window parent, const char *title, GCallback func, gpointer data, int default_w = -1, + int default_h = -1); + +ui::Table +create_dialog_table(unsigned int rows, unsigned int columns, unsigned int row_spacing, unsigned int col_spacing, + int border = 0); + +ui::Button create_dialog_button(const char *label, GCallback func, gpointer data); + +ui::VBox create_dialog_vbox(int spacing, int border = 0); + +ui::HBox create_dialog_hbox(int spacing, int border = 0); + +ui::Frame create_dialog_frame(const char *label, ui::Shadow shadow = ui::Shadow::ETCHED_IN); + +ui::Button create_modal_dialog_button(const char *label, ModalDialogButton &button); + +ui::Window create_modal_dialog_window(ui::Window parent, const char *title, ModalDialog &dialog, int default_w = -1, + int default_h = -1); + +ui::Window +create_fixedsize_modal_dialog_window(ui::Window parent, const char *title, ModalDialog &dialog, int width = -1, + int height = -1); + +EMessageBoxReturn modal_dialog_show(ui::Window window, ModalDialog &dialog); + + +gboolean dialog_button_ok(ui::Widget widget, ModalDialog *data); + +gboolean dialog_button_cancel(ui::Widget widget, ModalDialog *data); + +gboolean dialog_button_yes(ui::Widget widget, ModalDialog *data); + +gboolean dialog_button_no(ui::Widget widget, ModalDialog *data); + +gboolean dialog_delete_callback(ui::Widget widget, GdkEventAny *event, ModalDialog *data); + +ui::Window create_simple_modal_dialog_window(const char *title, ModalDialog &dialog, ui::Widget contents); + +class RadioHBox { +public: + ui::HBox m_hbox; + ui::RadioButton m_radio; + + RadioHBox(ui::HBox hbox, ui::RadioButton radio) : + m_hbox(hbox), + m_radio(radio) + { + } +}; + +RadioHBox RadioHBox_new(StringArrayRange names); + + +class PathEntry { +public: + ui::Frame m_frame; + ui::Entry m_entry; + ui::Button m_button; + + PathEntry(ui::Frame frame, ui::Entry entry, ui::Button button) : + m_frame(frame), + m_entry(entry), + m_button(button) + { + } +}; + +PathEntry PathEntry_new(); + +class BrowsedPathEntry { +public: + typedef Callback SetPathCallback; + typedef Callback BrowseCallback; + + PathEntry m_entry; + BrowseCallback m_browse; + + BrowsedPathEntry(const BrowseCallback &browse); +}; + +ui::Label DialogLabel_new(const char *name); + +ui::Table DialogRow_new(const char *name, ui::Widget widget); + +void DialogVBox_packRow(ui::Box vbox, ui::Widget row); + + +#endif diff --git a/libs/gtkutil/entry.cpp b/libs/gtkutil/entry.cpp new file mode 100644 index 0000000..acb74db --- /dev/null +++ b/libs/gtkutil/entry.cpp @@ -0,0 +1,37 @@ +#include "entry.h" + +#include + +void entry_set_string(ui::Entry entry, const char *string) +{ + gtk_entry_set_text(entry, string); +} + +void entry_set_int(ui::Entry entry, int i) +{ + char buf[32]; + sprintf(buf, "%d", i); + entry_set_string(entry, buf); +} + +void entry_set_float(ui::Entry entry, float f) +{ + char buf[32]; + sprintf(buf, "%g", f); + entry_set_string(entry, buf); +} + +const char *entry_get_string(ui::Entry entry) +{ + return gtk_entry_get_text(entry); +} + +int entry_get_int(ui::Entry entry) +{ + return atoi(entry_get_string(entry)); +} + +double entry_get_float(ui::Entry entry) +{ + return atof(entry_get_string(entry)); +} diff --git a/libs/gtkutil/entry.h b/libs/gtkutil/entry.h new file mode 100644 index 0000000..067157a --- /dev/null +++ b/libs/gtkutil/entry.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 + */ + +#include + +#if !defined( INCLUDED_GTKUTIL_ENTRY_H ) +#define INCLUDED_GTKUTIL_ENTRY_H + +void entry_set_string(ui::Entry entry, const char *string); + +void entry_set_int(ui::Entry entry, int i); + +void entry_set_float(ui::Entry entry, float f); + +const char *entry_get_string(ui::Entry entry); + +int entry_get_int(ui::Entry entry); + +double entry_get_float(ui::Entry entry); + +#endif diff --git a/libs/gtkutil/filechooser.cpp b/libs/gtkutil/filechooser.cpp new file mode 100644 index 0000000..03106b1 --- /dev/null +++ b/libs/gtkutil/filechooser.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 "filechooser.h" + +#include "ifiletypes.h" + +#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(ui::Window parent, bool open, const char *title, const char *path, const char *pattern, bool want_load, + bool want_import, bool want_save) +{ + filetype_t type; + + if (pattern == 0) { + pattern = "*"; + } + + FileTypeList typelist; + GlobalFiletypes().getTypeList(pattern, &typelist, want_load, want_import, want_save); + + GTKMasks masks(typelist); + + if (title == 0) { + title = open ? "Open File" : "Save File"; + } + + ui::Dialog dialog{ui::null}; + if (open) { + dialog = ui::Dialog::from(gtk_file_chooser_dialog_new(title, + parent, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL)); + } else { + dialog = ui::Dialog::from(gtk_file_chooser_dialog_new(title, + 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(dialog, TRUE); + gtk_window_set_position(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'; + } + + ui::Widget(dialog).destroy(); + + // don't return an empty filename + if (g_file_dialog_file[0] == '\0') { + return NULL; + } + + return g_file_dialog_file; +} + +char *dir_dialog(ui::Window parent, const char *title, const char *path) +{ + auto dialog = ui::Dialog::from(gtk_file_chooser_dialog_new(title, + parent, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL)); + + gtk_window_set_modal(dialog, TRUE); + gtk_window_set_position(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)); + } + + dialog.destroy(); + + return filename; +} + +const char * +file_dialog(ui::Window parent, bool open, const char *title, const char *path, const char *pattern, bool want_load, + bool want_import, bool want_save) +{ + for (;;) { + const char *file = file_dialog_show(parent, open, title, path, pattern, want_load, want_import, want_save); + + if (open + || !file + || !file_exists(file) + || ui::alert(parent, "The file specified already exists.\nDo you want to replace it?", title, + ui::alert_type::NOYES, ui::alert_icon::Question) == ui::alert_response::YES) { + return file; + } + } +} diff --git a/libs/gtkutil/filechooser.h b/libs/gtkutil/filechooser.h new file mode 100644 index 0000000..e2b2b25 --- /dev/null +++ b/libs/gtkutil/filechooser.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 + */ + +#include + +#if !defined( INCLUDED_GTKUTIL_FILECHOOSER_H ) +#define INCLUDED_GTKUTIL_FILECHOOSER_H + +/// \file +/// GTK+ file-chooser dialogs. + +const char *file_dialog(ui::Window parent, bool open, const char *title, const char *path = 0, const char *pattern = 0, + bool want_load = false, bool want_import = false, bool want_save = false); + + +/// \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(ui::Window 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 0000000..c32db75 --- /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 +#include + +ui::Frame create_framed_widget(ui::Widget widget) +{ + auto frame = ui::Frame(); + frame.show(); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + frame.add(widget); + widget.show(); + return frame; +} diff --git a/libs/gtkutil/frame.h b/libs/gtkutil/frame.h new file mode 100644 index 0000000..f35f9dc --- /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 + */ + +#include + +#if !defined( INCLUDED_GTKUTIL_FRAME_H ) +#define INCLUDED_GTKUTIL_FRAME_H + +ui::Frame create_framed_widget(ui::Widget widget); + +#endif diff --git a/libs/gtkutil/glfont.cpp b/libs/gtkutil/glfont.cpp new file mode 100644 index 0000000..711e663 --- /dev/null +++ b/libs/gtkutil/glfont.cpp @@ -0,0 +1,362 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" +#include "globaldefs.h" +#include "igl.h" + +// generic string printing with call lists +class GLFontCallList : public GLFont { + GLuint m_displayList; + int m_pixelHeight; + int m_pixelAscent; + int m_pixelDescent; +public: + GLFontCallList(GLuint displayList, int asc, int desc, int pixelHeight) : m_displayList(displayList), + m_pixelHeight(pixelHeight), + m_pixelAscent(asc), m_pixelDescent(desc) + { + } + + virtual ~GLFontCallList() + { + glDeleteLists(m_displayList, 256); + } + + void printString(const char *s) + { + GlobalOpenGL().m_glListBase(m_displayList); + GlobalOpenGL().m_glCallLists(GLsizei(strlen(s)), GL_UNSIGNED_BYTE, reinterpret_cast( s )); + } + + virtual int getPixelAscent() const + { + return m_pixelAscent; + } + + virtual int getPixelDescent() const + { + return m_pixelDescent; + } + + virtual int getPixelHeight() const + { + return m_pixelHeight; + } +}; + +#if GDEF_OS_WINDOWS +#include +#endif + +#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 new GLFontCallList( font_list_base, fontInfo->ascent, fontInfo->descent, fontInfo->ascent + fontInfo->descent ); +} + +#elif 0 + +#include + +GLFont *glfont_create( const char* font_string ){ + GLuint font_list_base = glGenLists( 256 ); + gint font_height = 0, font_ascent = 0, font_descent = 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 ) { + pango_font_description_free( font_desc ); + font_desc = pango_font_description_from_string( "fixed 8" ); + font = gdk_gl_font_use_pango_font( font_desc, 0, 256, font_list_base ); + } + + if ( font == 0 ) { + pango_font_description_free( font_desc ); + font_desc = pango_font_description_from_string( "courier new 8" ); + 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_ascent = pango_font_metrics_get_ascent( font_metrics ); + font_descent = pango_font_metrics_get_descent( font_metrics ); + font_height = font_ascent + font_descent; + + font_ascent = PANGO_PIXELS( font_ascent ); + font_descent = PANGO_PIXELS( font_descent ); + 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 > 256 ) { + font_height = 16; + } + + return new GLFontCallList( font_list_base, font_ascent, font_descent, font_height ); +} +#else + +// new font code ripped from ZeroRadiant + +#include +#include +#include + +class GLFontInternal : public GLFont { + const char *font_string; + int font_height; + int font_ascent; + int font_descent; + int y_offset_bitmap_render_pango_units; + PangoContext *ft2_context; + PangoFontMap *fontmap; + +public: + GLFontInternal(const char *_font_string) : font_string(_font_string) + { + PangoFontDescription *font_desc; + PangoLayout *layout; + PangoRectangle log_rect; + int font_ascent_pango_units; + int font_descent_pango_units; + +#if !PANGO_VERSION_CHECK(1, 22, 0) + ft2_context = pango_ft2_get_context( 72, 72 ); +#else + fontmap = pango_ft2_font_map_new(); + pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(fontmap), 72, 72); + ft2_context = pango_font_map_create_context(fontmap); +#endif + + font_desc = pango_font_description_from_string( "Liberation Mono 12" ); + //pango_font_description_set_size(font_desc, 10 * PANGO_SCALE); + pango_context_set_font_description(ft2_context, font_desc); + pango_font_description_free(font_desc); + // TODO fallback to fixed 8, courier new 8 + + layout = pango_layout_new(ft2_context); + +#ifdef FONT_SIZE_WORKAROUND + pango_layout_set_width( layout, -1 ); // -1 no wrapping. All text on one line. + pango_layout_set_text( layout, "The quick brown fox jumped over the lazy sleeping dog's back then sat on a tack.", -1 ); // -1 null-terminated string. +#endif + +#if !PANGO_VERSION_CHECK(1, 22, 0) + PangoLayoutIter *iter; + iter = pango_layout_get_iter( layout ); + font_ascent_pango_units = pango_layout_iter_get_baseline( iter ); + pango_layout_iter_free( iter ); +#else + font_ascent_pango_units = pango_layout_get_baseline(layout); +#endif + pango_layout_get_extents(layout, NULL, &log_rect); + g_object_unref(G_OBJECT(layout)); + font_descent_pango_units = log_rect.height - font_ascent_pango_units; + + font_ascent = PANGO_PIXELS_CEIL(font_ascent_pango_units); + font_descent = PANGO_PIXELS_CEIL(font_descent_pango_units); + font_height = font_ascent + font_descent; + y_offset_bitmap_render_pango_units = (font_ascent * PANGO_SCALE) - font_ascent_pango_units; + } + + virtual ~GLFontInternal() + { + g_object_unref(G_OBJECT(ft2_context)); + g_object_unref(G_OBJECT(fontmap)); + } + +// Renders the input text at the current location with the current color. +// The X position of the current location is used to place the left edge of the text image, +// where the text image bounds are defined as the logical extents of the line of text. +// The Y position of the current location is used to place the bottom of the text image. +// You should offset the Y position by the amount returned by gtk_glwidget_font_descent() +// if you want to place the baseline of the text image at the current Y position. +// Note: A problem with this function is that if the lower left corner of the text falls +// just a hair outside of the viewport (meaning the current raster position is invalid), +// then no text will be rendered. The solution to this is a very hacky one. You can search +// Google for "glDrawPixels clipping". + virtual void printString(const char *s) + { + // The idea for this code initially came from the font-pangoft2.c example that comes with GtkGLExt. + + PangoLayout *layout; + PangoRectangle log_rect; + FT_Bitmap bitmap; + unsigned char *begin_bitmap_buffer; + GLfloat color[4]; + GLint previous_unpack_alignment; + GLboolean previous_blend_enabled; + GLint previous_blend_func_src; + GLint previous_blend_func_dst; + GLfloat previous_red_bias; + GLfloat previous_green_bias; + GLfloat previous_blue_bias; + GLfloat previous_alpha_scale; + + layout = pango_layout_new(ft2_context); + pango_layout_set_width(layout, -1); // -1 no wrapping. All text on one line. + pango_layout_set_text(layout, s, -1); // -1 null-terminated string. + pango_layout_get_extents(layout, NULL, &log_rect); + + if (log_rect.width > 0 && log_rect.height > 0) { + bitmap.rows = font_ascent + font_descent; + bitmap.width = PANGO_PIXELS_CEIL(log_rect.width); + bitmap.pitch = -bitmap.width; // Rendering it "upside down" for OpenGL. + begin_bitmap_buffer = (unsigned char *) g_malloc(bitmap.rows * bitmap.width); + memset(begin_bitmap_buffer, 0, bitmap.rows * bitmap.width); + bitmap.buffer = begin_bitmap_buffer + (bitmap.rows - 1) * bitmap.width; // See pitch above. + bitmap.num_grays = 0xff; + bitmap.pixel_mode = FT_PIXEL_MODE_GRAY; + pango_ft2_render_layout_subpixel(&bitmap, layout, -log_rect.x, + y_offset_bitmap_render_pango_units); + GlobalOpenGL().m_glGetFloatv(GL_CURRENT_COLOR, color); + + // Save state. I didn't see any OpenGL push/pop operations for these. + // Question: Is saving/restoring this state necessary? Being safe. + GlobalOpenGL().m_glGetIntegerv(GL_UNPACK_ALIGNMENT, &previous_unpack_alignment); + previous_blend_enabled = GlobalOpenGL().m_glIsEnabled(GL_BLEND); + GlobalOpenGL().m_glGetIntegerv(GL_BLEND_SRC, &previous_blend_func_src); + GlobalOpenGL().m_glGetIntegerv(GL_BLEND_DST, &previous_blend_func_dst); + GlobalOpenGL().m_glGetFloatv(GL_RED_BIAS, &previous_red_bias); + GlobalOpenGL().m_glGetFloatv(GL_GREEN_BIAS, &previous_green_bias); + GlobalOpenGL().m_glGetFloatv(GL_BLUE_BIAS, &previous_blue_bias); + GlobalOpenGL().m_glGetFloatv(GL_ALPHA_SCALE, &previous_alpha_scale); + + GlobalOpenGL().m_glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + GlobalOpenGL().m_glEnable(GL_BLEND); + GlobalOpenGL().m_glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GlobalOpenGL().m_glPixelTransferf(GL_RED_BIAS, color[0]); + GlobalOpenGL().m_glPixelTransferf(GL_GREEN_BIAS, color[1]); + GlobalOpenGL().m_glPixelTransferf(GL_BLUE_BIAS, color[2]); + GlobalOpenGL().m_glPixelTransferf(GL_ALPHA_SCALE, color[3]); + + GlobalOpenGL().m_glDrawPixels(bitmap.width, bitmap.rows, + GL_ALPHA, GL_UNSIGNED_BYTE, begin_bitmap_buffer); + g_free(begin_bitmap_buffer); + + // Restore state in reverse order of how we set it. + GlobalOpenGL().m_glPixelTransferf(GL_ALPHA_SCALE, previous_alpha_scale); + GlobalOpenGL().m_glPixelTransferf(GL_BLUE_BIAS, previous_blue_bias); + GlobalOpenGL().m_glPixelTransferf(GL_GREEN_BIAS, previous_green_bias); + GlobalOpenGL().m_glPixelTransferf(GL_RED_BIAS, previous_red_bias); + GlobalOpenGL().m_glBlendFunc(previous_blend_func_src, previous_blend_func_dst); + if (!previous_blend_enabled) { + GlobalOpenGL().m_glDisable(GL_BLEND); + } + GlobalOpenGL().m_glPixelStorei(GL_UNPACK_ALIGNMENT, previous_unpack_alignment); + } + + g_object_unref(G_OBJECT(layout)); + } + + virtual int getPixelAscent() const + { + return font_ascent; + } + + virtual int getPixelDescent() const + { + return font_descent; + } + + virtual int getPixelHeight() const + { + return font_height; + } +}; + +GLFont *glfont_create(const char *font_string) +{ + return new GLFontInternal(font_string); +} + +#endif diff --git a/libs/gtkutil/glfont.h b/libs/gtkutil/glfont.h new file mode 100644 index 0000000..ca39a6a --- /dev/null +++ b/libs/gtkutil/glfont.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_GTKUTIL_GLFONT_H ) +#define INCLUDED_GTKUTIL_GLFONT_H + +typedef unsigned int GLuint; + +class GLFont { +public: + virtual int getPixelHeight() const = 0; + + virtual int getPixelAscent() const = 0; + + virtual int getPixelDescent() const = 0; + + virtual void printString(const char *s) = 0; + + virtual ~GLFont() + { + } +}; + +GLFont *glfont_create(const char *font_string); +// release with delete + +#endif diff --git a/libs/gtkutil/glwidget.cpp b/libs/gtkutil/glwidget.cpp new file mode 100644 index 0000000..52cec49 --- /dev/null +++ b/libs/gtkutil/glwidget.cpp @@ -0,0 +1,304 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 "igl.h" + +void (*GLWidget_sharedContextCreated)() = 0; + +void (*GLWidget_sharedContextDestroyed)() = 0; + +unsigned int g_context_count = 0; + +ui::GLArea g_shared{ui::null}; + +void _glwidget_context_created(ui::GLArea self, void *data) +{ + if (++g_context_count == 1) { + g_shared = self; + g_object_ref(g_shared._handle); + + glwidget_make_current(g_shared); + GlobalOpenGL().contextValid = true; + + GLWidget_sharedContextCreated(); + } +} + +void _glwidget_context_destroyed(ui::GLArea self, void *data) +{ + if (--g_context_count == 0) { + GlobalOpenGL().contextValid = false; + + GLWidget_sharedContextDestroyed(); + + g_shared.unref(); + g_shared = ui::GLArea(ui::null); + } +} + +void glwidget_destroy_context(ui::GLArea self) +{ +} + +void glwidget_create_context(ui::GLArea self) +{ +} + +#if GTK_TARGET == 3 + +#include + +GdkGLContext *glwidget_context_created(ui::GLArea self) +{ + _glwidget_context_created(self, nullptr); + return gtk_gl_area_get_context(self); +} + +ui::GLArea glwidget_new(bool zbuffer) +{ + auto self = ui::GLArea(GTK_GL_AREA(gtk_gl_area_new())); + gtk_gl_area_set_has_depth_buffer(self, zbuffer); + gtk_gl_area_set_auto_render(self, false); + + self.connect("realize", G_CALLBACK(glwidget_context_created), nullptr); + return self; +} + +bool glwidget_make_current(ui::GLArea self) +{ +// if (!g_context_count) { +// glwidget_context_created(self); +// } + gtk_gl_area_make_current(self); + auto valid = GlobalOpenGL().contextValid; + return true; +} + +void glwidget_swap_buffers(ui::GLArea self) +{ + gtk_gl_area_queue_render(self); +} + +#endif + +#if GTK_TARGET == 2 + +#include +#include + +#include "pointer.h" + +struct config_t { + const char *name; + int *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() +{ + for (configs_iterator i = configs, end = configs + 2; i != end; ++i) { + if (auto glconfig = gdk_gl_config_new(i->attribs)) { + 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() +{ + for (configs_iterator i = configs_with_depth, end = configs_with_depth + 6; i != end; ++i) { + if (auto glconfig = gdk_gl_config_new(i->attribs)) { + 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)); +} + +int glwidget_context_created(ui::GLArea self, void *data) +{ + _glwidget_context_created(self, data); + return false; +} + +int glwidget_context_destroyed(ui::GLArea self, void *data) +{ + _glwidget_context_destroyed(self, data); + return false; +} + +bool glwidget_enable_gl(ui::GLArea self, ui::Widget root, gpointer data) +{ + if (!root && !gtk_widget_is_gl_capable(self)) { + const auto zbuffer = g_object_get_data(G_OBJECT(self), "zbuffer"); + GdkGLConfig *glconfig = zbuffer ? glconfig_new_with_depth() : glconfig_new(); + ASSERT_MESSAGE(glconfig, "failed to create OpenGL config"); + + const auto share_list = g_shared ? gtk_widget_get_gl_context(g_shared) : nullptr; + gtk_widget_set_gl_capability(self, glconfig, share_list, true, GDK_GL_RGBA_TYPE); + + gtk_widget_realize(self); + if (!g_shared) { + g_shared = self; + } + // free glconfig? + } + return false; +} + +ui::GLArea glwidget_new(bool zbuffer) +{ + auto self = ui::GLArea::from(gtk_drawing_area_new()); + + g_object_set_data(G_OBJECT(self), "zbuffer", gint_to_pointer(zbuffer)); + + self.connect("hierarchy-changed", G_CALLBACK(glwidget_enable_gl), 0); + + self.connect("realize", G_CALLBACK(glwidget_context_created), 0); + self.connect("unrealize", G_CALLBACK(glwidget_context_destroyed), 0); + + return self; +} + +void glwidget_swap_buffers(ui::GLArea self) +{ + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(self); + gdk_gl_drawable_swap_buffers(gldrawable); +} + +bool glwidget_make_current(ui::GLArea self) +{ + GdkGLContext *glcontext = gtk_widget_get_gl_context(self); + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(self); + return gdk_gl_drawable_gl_begin(gldrawable, glcontext); +} + +#endif diff --git a/libs/gtkutil/glwidget.h b/libs/gtkutil/glwidget.h new file mode 100644 index 0000000..c0bbff4 --- /dev/null +++ b/libs/gtkutil/glwidget.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 + */ + +#include + +#if !defined( INCLUDED_GTKUTIL_GLWIDGET_H ) +#define INCLUDED_GTKUTIL_GLWIDGET_H + +extern void (*GLWidget_sharedContextCreated)(); + +extern void (*GLWidget_sharedContextDestroyed)(); + +ui::GLArea glwidget_new(bool zbuffer); + +void glwidget_create_context(ui::GLArea self); + +void glwidget_destroy_context(ui::GLArea self); + +bool glwidget_make_current(ui::GLArea self); + +void glwidget_swap_buffers(ui::GLArea self); + +#endif diff --git a/libs/gtkutil/idledraw.h b/libs/gtkutil/idledraw.h new file mode 100644 index 0000000..6c29836 --- /dev/null +++ b/libs/gtkutil/idledraw.h @@ -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 + */ + +#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 0000000..16c03c5 --- /dev/null +++ b/libs/gtkutil/image.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "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, FALSE, 255, 0, 255); + g_object_unref(rgb); + return rgba; + } +} + +ui::Image image_new_from_file_with_mask(const char *filename) +{ + GdkPixbuf *rgba = pixbuf_new_from_file_with_mask(filename); + if (rgba == 0) { + return ui::Image(ui::null); + } else { + auto image = ui::Image::from(gtk_image_new_from_pixbuf(rgba)); + g_object_unref(rgba); + return image; + } +} + +ui::Image image_new_missing() +{ + return ui::Image::from(gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_SMALL_TOOLBAR)); +} + +ui::Image new_image(const char *filename) +{ + if (auto image = image_new_from_file_with_mask(filename)) { + return image; + } + return image_new_missing(); +} + +ui::Image 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 0000000..6604df2 --- /dev/null +++ b/libs/gtkutil/image.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 + */ + +#include + +#if !defined( INCLUDED_GTKUTIL_IMAGE_H ) +#define INCLUDED_GTKUTIL_IMAGE_H + +void BitmapsPath_set(const char *path); + +typedef struct _GdkPixbuf GdkPixbuf; + +GdkPixbuf *pixbuf_new_from_file_with_mask(const char *filename); + +ui::Image image_new_from_file_with_mask(const char *filename); + +ui::Image image_new_missing(); + +ui::Image new_image(const char *filename); // filename is full path to image file +ui::Image 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 0000000..8cd72a9 --- /dev/null +++ b/libs/gtkutil/menu.cpp @@ -0,0 +1,275 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "generic/callback.h" + +#include "accelerator.h" +#include "closure.h" +#include "container.h" +#include "pointer.h" + +void menu_add_item(ui::Menu menu, ui::MenuItem item) +{ + menu.add(item); +} + +ui::MenuItem menu_separator(ui::Menu menu) +{ + auto menu_item = ui::MenuItem::from(gtk_menu_item_new()); + menu.add(menu_item); + gtk_widget_set_sensitive(menu_item, FALSE); + menu_item.show(); + return menu_item; +} + +ui::TearoffMenuItem menu_tearoff(ui::Menu menu) +{ + auto menu_item = ui::TearoffMenuItem::from(gtk_tearoff_menu_item_new()); + menu.add(menu_item); +// gtk_widget_set_sensitive(menu_item, FALSE); -- controls whether menu is detachable + menu_item.show(); + return menu_item; +} + +ui::MenuItem new_sub_menu_item_with_mnemonic(const char *mnemonic) +{ + auto item = ui::MenuItem(mnemonic, true); + item.show(); + + auto sub_menu = ui::Menu(ui::New); + gtk_menu_item_set_submenu(item, sub_menu); + + return item; +} + +ui::Menu create_sub_menu_with_mnemonic(ui::MenuShell parent, const char *mnemonic) +{ + auto item = new_sub_menu_item_with_mnemonic(mnemonic); + parent.add(item); + return ui::Menu::from(gtk_menu_item_get_submenu(item)); +} + +ui::Menu create_sub_menu_with_mnemonic(ui::MenuBar bar, const char *mnemonic) +{ + return create_sub_menu_with_mnemonic(ui::MenuShell::from(bar._handle), mnemonic); +} + +ui::Menu create_sub_menu_with_mnemonic(ui::Menu parent, const char *mnemonic) +{ + return create_sub_menu_with_mnemonic(ui::MenuShell::from(parent._handle), mnemonic); +} + +void activate_closure_callback(ui::Widget widget, gpointer data) +{ + (*reinterpret_cast *>( data ))(); +} + +guint menu_item_connect_callback(ui::MenuItem 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(ui::CheckMenuItem 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; +} + +ui::MenuItem new_menu_item_with_mnemonic(const char *mnemonic, const Callback &callback) +{ + auto item = ui::MenuItem(mnemonic, true); + item.show(); + menu_item_connect_callback(item, callback); + return item; +} + +ui::MenuItem create_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Callback &callback) +{ + auto item = new_menu_item_with_mnemonic(mnemonic, callback); + menu.add(item); + return item; +} + +ui::CheckMenuItem new_check_menu_item_with_mnemonic(const char *mnemonic, const Callback &callback) +{ + auto item = ui::CheckMenuItem::from(gtk_check_menu_item_new_with_mnemonic(mnemonic)); + item.show(); + check_menu_item_connect_callback(item, callback); + return item; +} + +ui::CheckMenuItem +create_check_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Callback &callback) +{ + auto item = new_check_menu_item_with_mnemonic(mnemonic, callback); + menu.add(item); + return item; +} + +ui::RadioMenuItem +new_radio_menu_item_with_mnemonic(GSList **group, const char *mnemonic, const Callback &callback) +{ + auto item = ui::RadioMenuItem::from(gtk_radio_menu_item_new_with_mnemonic(*group, mnemonic)); + if (*group == 0) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); + } + *group = gtk_radio_menu_item_get_group(item); + item.show(); + check_menu_item_connect_callback(item, callback); + return item; +} + +ui::RadioMenuItem create_radio_menu_item_with_mnemonic(ui::Menu menu, GSList **group, const char *mnemonic, + const Callback &callback) +{ + auto item = new_radio_menu_item_with_mnemonic(group, mnemonic, callback); + menu.add(item); + return item; +} + +void check_menu_item_set_active_no_signal(ui::CheckMenuItem 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(ui::RadioMenuItem 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(ui::MenuItem 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_add_accelerator(ui::MenuItem item, Accelerator accelerator) +{ + if (accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers)) { + GClosure *closure = global_accel_group_find(accelerator); + ASSERT_NOTNULL(closure); + menu_item_set_accelerator(item, closure); + } +} + +ui::MenuItem create_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Command &command) +{ + auto 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(void *it, bool enabled) +{ + auto item = ui::CheckMenuItem::from(it); + check_menu_item_set_active_no_signal(item, enabled); +} + +ui::CheckMenuItem create_check_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Toggle &toggle) +{ + auto item = create_check_menu_item_with_mnemonic(menu, mnemonic, toggle.m_command.m_callback); + menu_item_add_accelerator(item, toggle.m_command.m_accelerator); + toggle.m_exportCallback(PointerCaller(item._handle)); + return item; +} diff --git a/libs/gtkutil/menu.h b/libs/gtkutil/menu.h new file mode 100644 index 0000000..08f8224 --- /dev/null +++ b/libs/gtkutil/menu.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_GTKUTIL_MENU_H ) +#define INCLUDED_GTKUTIL_MENU_H + +#include +#include "generic/callback.h" + +typedef int gint; +typedef gint gboolean; +typedef struct _GSList GSList; + +void menu_add_item(ui::Menu menu, ui::MenuItem item); + +ui::MenuItem menu_separator(ui::Menu menu); + +ui::TearoffMenuItem menu_tearoff(ui::Menu menu); + +ui::MenuItem new_sub_menu_item_with_mnemonic(const char *mnemonic); + +ui::Menu create_sub_menu_with_mnemonic(ui::MenuBar bar, const char *mnemonic); + +ui::Menu create_sub_menu_with_mnemonic(ui::Menu parent, const char *mnemonic); + +ui::MenuItem create_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Callback &callback); + +ui::CheckMenuItem +create_check_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Callback &callback); + +ui::RadioMenuItem create_radio_menu_item_with_mnemonic(ui::Menu menu, GSList **group, const char *mnemonic, + const Callback &callback); + +class Command; + +ui::MenuItem create_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Command &command); + +class Toggle; + +ui::CheckMenuItem create_check_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const Toggle &toggle); + + +void check_menu_item_set_active_no_signal(ui::CheckMenuItem item, gboolean active); + +void radio_menu_item_set_active_no_signal(ui::RadioMenuItem item, gboolean active); + +#endif diff --git a/libs/gtkutil/messagebox.cpp b/libs/gtkutil/messagebox.cpp new file mode 100644 index 0000000..9b9b899 --- /dev/null +++ b/libs/gtkutil/messagebox.cpp @@ -0,0 +1,195 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "dialog.h" +#include "widget.h" + +ui::Widget create_padding(int width, int height) +{ + ui::Alignment widget = ui::Alignment(0.0, 0.0, 0.0, 0.0); + widget.show(); + widget.dimensions(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(ui::Window parentWindow, 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); + + ui::Window window = create_fixedsize_modal_dialog_window(parentWindow, title, dialog, 400, 100); + + if (parentWindow) { + //window.connect( "delete_event", G_CALLBACK(floating_window_delete_present), parent); + gtk_window_deiconify(parentWindow); + } + + auto accel = ui::AccelGroup(ui::New); + window.add_accel_group(accel); + + auto vbox = create_dialog_vbox(8, 8); + window.add(vbox); + + + auto hboxDummy = create_dialog_hbox(0, 0); + vbox.pack_start(hboxDummy, FALSE, FALSE, 0); + + hboxDummy.pack_start(create_padding(0, 50), FALSE, FALSE, 0); // HACK to force minimum height + + auto iconBox = create_dialog_hbox(16, 0); + hboxDummy.pack_start(iconBox, FALSE, FALSE, 0); + + auto image = ui::Image::from(gtk_image_new_from_stock(messagebox_stock_icon(icon), GTK_ICON_SIZE_DIALOG)); + image.show(); + iconBox.pack_start(image, FALSE, FALSE, 0); + + auto label = ui::Label(text); + label.show(); + 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); + iconBox.pack_start(label, TRUE, TRUE, 0); + + + auto vboxDummy = create_dialog_vbox(0, 0); + vbox.pack_start(vboxDummy, FALSE, FALSE, 0); + + auto alignment = ui::Alignment(0.5, 0.0, 0.0, 0.0); + alignment.show(); + vboxDummy.pack_start(alignment, FALSE, FALSE, 0); + + auto hbox = create_dialog_hbox(8, 0); + alignment.add(hbox); + + vboxDummy.pack_start(create_padding(400, 0), FALSE, FALSE, 0); // HACK to force minimum width + + + if (type == eMB_OK) { + auto button = create_modal_dialog_button("OK", ok_button); + hbox.pack_start(button, TRUE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, (GtkAccelFlags) 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, (GtkAccelFlags) 0); + widget_make_default(button); + button.show(); + + dialog.ret = eIDOK; + } else if (type == eMB_OKCANCEL) { + { + auto button = create_modal_dialog_button("OK", ok_button); + hbox.pack_start(button, TRUE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, + (GtkAccelFlags) 0); + widget_make_default(button); + button.show(); + } + + { + auto button = create_modal_dialog_button("OK", cancel_button); + hbox.pack_start(button, TRUE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, + (GtkAccelFlags) 0); + button.show(); + } + + dialog.ret = eIDCANCEL; + } else if (type == eMB_YESNOCANCEL) { + { + auto button = create_modal_dialog_button("Yes", yes_button); + hbox.pack_start(button, TRUE, FALSE, 0); + widget_make_default(button); + button.show(); + } + + { + auto button = create_modal_dialog_button("No", no_button); + hbox.pack_start(button, TRUE, FALSE, 0); + button.show(); + } + { + auto button = create_modal_dialog_button("Cancel", cancel_button); + hbox.pack_start(button, TRUE, FALSE, 0); + button.show(); + } + + dialog.ret = eIDCANCEL; + } else if (type == eMB_NOYES) { + { + auto button = create_modal_dialog_button("No", no_button); + hbox.pack_start(button, TRUE, FALSE, 0); + widget_make_default(button); + button.show(); + } + { + auto button = create_modal_dialog_button("Yes", yes_button); + hbox.pack_start(button, TRUE, FALSE, 0); + button.show(); + } + + dialog.ret = eIDNO; + } else /* if (type == eMB_YESNO) */ + { + { + auto button = create_modal_dialog_button("Yes", yes_button); + hbox.pack_start(button, TRUE, FALSE, 0); + widget_make_default(button); + button.show(); + } + + { + auto button = create_modal_dialog_button("No", no_button); + hbox.pack_start(button, TRUE, FALSE, 0); + button.show(); + } + dialog.ret = eIDNO; + } + + modal_dialog_show(window, dialog); + + window.destroy(); + + return dialog.ret; +} diff --git a/libs/gtkutil/messagebox.h b/libs/gtkutil/messagebox.h new file mode 100644 index 0000000..0935b6e --- /dev/null +++ b/libs/gtkutil/messagebox.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_GTKUTIL_MESSAGEBOX_H ) +#define INCLUDED_GTKUTIL_MESSAGEBOX_H + +#include "qerplugin.h" + +/// \brief Shows a modal message-box. +EMessageBoxReturn +gtk_MessageBox(ui::Window parent, const char *text, const char *title = "WorldSpawn", 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 0000000..f9da2db --- /dev/null +++ b/libs/gtkutil/nonmodal.cpp @@ -0,0 +1,112 @@ +#include "nonmodal.h" + +#include +#include + +gboolean escape_clear_focus_widget(ui::Widget widget, GdkEventKey *event, gpointer data) +{ + if (event->keyval == GDK_KEY_Escape) { + gtk_window_set_focus(widget.window(), NULL); + return TRUE; + } + return FALSE; +} + +void widget_connect_escape_clear_focus_widget(ui::Widget widget) +{ + widget.connect("key_press_event", G_CALLBACK(escape_clear_focus_widget), 0); +} + +gboolean NonModalEntry::focus_in(ui::Entry entry, GdkEventFocus *event, NonModalEntry *self) +{ + self->m_editing = false; + return FALSE; +} + +gboolean NonModalEntry::focus_out(ui::Entry entry, GdkEventFocus *event, NonModalEntry *self) +{ + if (self->m_editing && gtk_widget_get_visible(entry)) { + self->m_apply(); + } + self->m_editing = false; + return FALSE; +} + +gboolean NonModalEntry::changed(ui::Entry entry, NonModalEntry *self) +{ + self->m_editing = true; + return FALSE; +} + +gboolean NonModalEntry::enter(ui::Entry entry, GdkEventKey *event, NonModalEntry *self) +{ + if (event->keyval == GDK_KEY_Return) { + self->m_apply(); + self->m_editing = false; + gtk_window_set_focus(entry.window(), NULL); + return TRUE; + } + return FALSE; +} + +gboolean NonModalEntry::escape(ui::Entry entry, GdkEventKey *event, NonModalEntry *self) +{ + if (event->keyval == GDK_KEY_Escape) { + self->m_cancel(); + self->m_editing = false; + gtk_window_set_focus(entry.window(), NULL); + return TRUE; + } + return FALSE; +} + +void NonModalEntry::connect(ui::Entry entry) +{ + entry.connect("focus_in_event", G_CALLBACK(focus_in), this); + entry.connect("focus_out_event", G_CALLBACK(focus_out), this); + entry.connect("key_press_event", G_CALLBACK(enter), this); + entry.connect("key_press_event", G_CALLBACK(escape), this); + entry.connect("changed", G_CALLBACK(changed), this); +} + +gboolean NonModalSpinner::changed(ui::SpinButton spin, NonModalSpinner *self) +{ + self->m_apply(); + return FALSE; +} + +gboolean NonModalSpinner::enter(ui::SpinButton spin, GdkEventKey *event, NonModalSpinner *self) +{ + if (event->keyval == GDK_KEY_Return) { + gtk_window_set_focus(spin.window(), NULL); + return TRUE; + } + return FALSE; +} + +gboolean NonModalSpinner::escape(ui::SpinButton spin, GdkEventKey *event, NonModalSpinner *self) +{ + if (event->keyval == GDK_KEY_Escape) { + self->m_cancel(); + gtk_window_set_focus(spin.window(), NULL); + return TRUE; + } + return FALSE; +} + +void NonModalSpinner::connect(ui::SpinButton spin) +{ + auto adj = ui::Adjustment::from(gtk_spin_button_get_adjustment(spin)); + guint handler = adj.connect("value_changed", G_CALLBACK(changed), this); + g_object_set_data(G_OBJECT(spin), "handler", gint_to_pointer(handler)); + spin.connect("key_press_event", G_CALLBACK(enter), this); + spin.connect("key_press_event", G_CALLBACK(escape), this); +} + +void NonModalRadio::connect(ui::RadioButton radio) +{ + GSList *group = gtk_radio_button_get_group(radio); + for (; group != 0; group = g_slist_next(group)) { + toggle_button_connect_callback(ui::ToggleButton::from(group->data), m_changed); + } +} diff --git a/libs/gtkutil/nonmodal.h b/libs/gtkutil/nonmodal.h new file mode 100644 index 0000000..cb3f08d --- /dev/null +++ b/libs/gtkutil/nonmodal.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_GTKUTIL_NONMODAL_H ) +#define INCLUDED_GTKUTIL_NONMODAL_H + +#include +#include "generic/callback.h" + +#include "pointer.h" +#include "button.h" + +gboolean escape_clear_focus_widget(ui::Widget widget, GdkEventKey *event, gpointer data); + +void widget_connect_escape_clear_focus_widget(ui::Widget widget); + +class NonModalEntry { + bool m_editing; + Callback m_apply; + Callback m_cancel; + + static gboolean focus_in(ui::Entry entry, GdkEventFocus *event, NonModalEntry *self); + + static gboolean focus_out(ui::Entry entry, GdkEventFocus *event, NonModalEntry *self); + + static gboolean changed(ui::Entry entry, NonModalEntry *self); + + static gboolean enter(ui::Entry entry, GdkEventKey *event, NonModalEntry *self); + + static gboolean escape(ui::Entry entry, GdkEventKey *event, NonModalEntry *self); + +public: + NonModalEntry(const Callback &apply, const Callback &cancel) : m_editing(false), m_apply(apply), + m_cancel(cancel) + { + } + + void connect(ui::Entry entry); +}; + + +class NonModalSpinner { + Callback m_apply; + Callback m_cancel; + + static gboolean changed(ui::SpinButton spin, NonModalSpinner *self); + + static gboolean enter(ui::SpinButton spin, GdkEventKey *event, NonModalSpinner *self); + + static gboolean escape(ui::SpinButton spin, GdkEventKey *event, NonModalSpinner *self); + +public: + NonModalSpinner(const Callback &apply, const Callback &cancel) : m_apply(apply), m_cancel(cancel) + { + } + + void connect(ui::SpinButton spin); +}; + + +class NonModalRadio { + Callback m_changed; + +public: + NonModalRadio(const Callback &changed) : m_changed(changed) + { + } + + void connect(ui::RadioButton radio); +}; + + +#endif diff --git a/libs/gtkutil/paned.cpp b/libs/gtkutil/paned.cpp new file mode 100644 index 0000000..b387c65 --- /dev/null +++ b/libs/gtkutil/paned.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 "paned.h" + +#include +#include + +#include "frame.h" + + +class PanedState { +public: + float position; + int size; +}; + +gboolean hpaned_allocate(ui::Widget 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(ui::Widget 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(ui::Widget 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,}; + +ui::HPaned create_split_views(ui::Widget topleft, ui::Widget topright, ui::Widget botleft, ui::Widget botright) +{ + auto hsplit = ui::HPaned(ui::New); + hsplit.show(); + + hsplit.connect("size_allocate", G_CALLBACK(hpaned_allocate), &g_hpaned); + hsplit.connect("notify::position", G_CALLBACK(paned_position), &g_hpaned); + + { + auto vsplit = ui::VPaned(ui::New); + gtk_paned_add1(GTK_PANED(hsplit), vsplit); + vsplit.show(); + + vsplit.connect("size_allocate", G_CALLBACK(vpaned_allocate), &g_vpaned1); + vsplit.connect("notify::position", G_CALLBACK(paned_position), &g_vpaned1); + + gtk_paned_add1(GTK_PANED(vsplit), create_framed_widget(topleft)); + gtk_paned_add2(GTK_PANED(vsplit), create_framed_widget(topright)); + } + { + auto vsplit = ui::VPaned(ui::New); + gtk_paned_add2(GTK_PANED(hsplit), vsplit); + vsplit.show(); + + vsplit.connect("size_allocate", G_CALLBACK(vpaned_allocate), &g_vpaned2); + vsplit.connect("notify::position", G_CALLBACK(paned_position), &g_vpaned2); + + gtk_paned_add1(GTK_PANED(vsplit), create_framed_widget(botleft)); + gtk_paned_add2(GTK_PANED(vsplit), create_framed_widget(botright)); + } + return hsplit; +} diff --git a/libs/gtkutil/paned.h b/libs/gtkutil/paned.h new file mode 100644 index 0000000..1c80ae6 --- /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 + */ + +#include + +#if !defined( INCLUDED_GTKUTIL_PANED_H ) +#define INCLUDED_GTKUTIL_PANED_H + +ui::HPaned create_split_views(ui::Widget topleft, ui::Widget topright, ui::Widget botleft, ui::Widget botright); + +#endif diff --git a/libs/gtkutil/pointer.h b/libs/gtkutil/pointer.h new file mode 100644 index 0000000..45e3eb0 --- /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 0000000..fa7cc4f --- /dev/null +++ b/libs/gtkutil/toolbar.cpp @@ -0,0 +1,79 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "image.h" + + +void toolbar_append(ui::Toolbar toolbar, ui::ToolItem button, const char *description) +{ + gtk_widget_show_all(button); + gtk_widget_set_tooltip_text(button, description); + toolbar.add(button); +} + +ui::ToolButton +toolbar_append_button(ui::Toolbar toolbar, const char *description, const char *icon, const Callback &callback) +{ + auto button = ui::ToolButton::from(gtk_tool_button_new(new_local_image(icon), nullptr)); + button_connect_callback(button, callback); + toolbar_append(toolbar, button, description); + return button; +} + +ui::ToggleToolButton toolbar_append_toggle_button(ui::Toolbar toolbar, const char *description, const char *icon, + const Callback &callback) +{ + auto button = ui::ToggleToolButton::from(gtk_toggle_tool_button_new()); + toggle_button_connect_callback(button, callback); + gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(button), new_local_image(icon)); + gtk_tool_button_set_label(GTK_TOOL_BUTTON(button), description); + toolbar_append(toolbar, button, description); + return button; +} + +ui::ToolButton +toolbar_append_button(ui::Toolbar 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(void *it, bool active) +{ + auto button = ui::ToggleToolButton::from(it); + toggle_button_set_active_no_signal(button, active); +} + +ui::ToggleToolButton +toolbar_append_toggle_button(ui::Toolbar toolbar, const char *description, const char *icon, const Toggle &toggle) +{ + auto button = toolbar_append_toggle_button(toolbar, description, icon, toggle.m_command.m_callback); + toggle.m_exportCallback(PointerCaller(button._handle)); + return button; +} diff --git a/libs/gtkutil/toolbar.h b/libs/gtkutil/toolbar.h new file mode 100644 index 0000000..b016ab2 --- /dev/null +++ b/libs/gtkutil/toolbar.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_TOOLBAR_H ) +#define INCLUDED_GTKUTIL_TOOLBAR_H + +#include +#include "generic/callback.h" + +class Command; + +class Toggle; + +ui::ToolButton +toolbar_append_button(ui::Toolbar toolbar, const char *description, const char *icon, const Callback &callback); + +ui::ToolButton +toolbar_append_button(ui::Toolbar toolbar, const char *description, const char *icon, const Command &command); + +ui::ToggleToolButton toolbar_append_toggle_button(ui::Toolbar toolbar, const char *description, const char *icon, + const Callback &callback); + +ui::ToggleToolButton +toolbar_append_toggle_button(ui::Toolbar 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 0000000..9d9c1a8 --- /dev/null +++ b/libs/gtkutil/widget.cpp @@ -0,0 +1,86 @@ +#include "widget.h" +#include + +void widget_queue_draw(ui::Widget &widget) +{ + gtk_widget_queue_draw(widget); +} + +void widget_make_default(ui::Widget widget) +{ + gtk_widget_set_can_default(widget, true); + gtk_widget_grab_default(widget); +} + +gboolean ToggleShown::notify_visible(ui::Widget widget, gpointer dummy, ToggleShown *self) +{ + self->update(); + return FALSE; +} + +gboolean ToggleShown::destroy(ui::Widget widget, ToggleShown *self) +{ + self->m_shownDeferred = gtk_widget_get_visible(self->m_widget) != FALSE; + self->m_widget = ui::Widget(ui::null); + return FALSE; +} + +void ToggleShown::update() +{ + m_item.update(); +} + +bool ToggleShown::active() const +{ + if (!m_widget) { + return m_shownDeferred; + } else { + return gtk_widget_get_visible(m_widget) != FALSE; + } +} + +void ToggleShown::exportActive(const Callback &importCallback) +{ + importCallback(active()); +} + +void ToggleShown::set(bool shown) +{ + if (!m_widget) { + m_shownDeferred = shown; + } else { + m_widget.visible(shown); + } +} + +void ToggleShown::toggle() +{ + m_widget.visible(!m_widget.visible()); +} + +void ToggleShown::connect(ui::Widget widget) +{ + m_widget = widget; + m_widget.visible(m_shownDeferred); + m_widget.connect("notify::visible", G_CALLBACK(notify_visible), this); + m_widget.connect("destroy", G_CALLBACK(destroy), this); + update(); +} + +gboolean WidgetFocusPrinter::focus_in(ui::Widget widget, GdkEventFocus *event, WidgetFocusPrinter *self) +{ + globalOutputStream() << self->m_name << " takes focus\n"; + return FALSE; +} + +gboolean WidgetFocusPrinter::focus_out(ui::Widget widget, GdkEventFocus *event, WidgetFocusPrinter *self) +{ + globalOutputStream() << self->m_name << " loses focus\n"; + return FALSE; +} + +void WidgetFocusPrinter::connect(ui::Widget widget) +{ + widget.connect("focus_in_event", G_CALLBACK(focus_in), this); + widget.connect("focus_out_event", G_CALLBACK(focus_out), this); +} diff --git a/libs/gtkutil/widget.h b/libs/gtkutil/widget.h new file mode 100644 index 0000000..28395b3 --- /dev/null +++ b/libs/gtkutil/widget.h @@ -0,0 +1,117 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "generic/callback.h" +#include "warnings.h" +#include "debugging/debugging.h" +#include "property.h" + +class ToggleItem { + Callback &)> m_exportCallback; + typedef std::list> ImportCallbacks; + ImportCallbacks m_importCallbacks; +public: + ToggleItem(const Callback &)> &exportCallback) : m_exportCallback(exportCallback) + { + } + + void update() + { + for (ImportCallbacks::iterator i = m_importCallbacks.begin(); i != m_importCallbacks.end(); ++i) { + m_exportCallback(*i); + } + } + + void addCallback(const Callback &callback) + { + m_importCallbacks.push_back(callback); + m_exportCallback(callback); + } + + typedef MemberCaller &), &ToggleItem::addCallback> AddCallbackCaller; +}; + +class ToggleShown { + bool m_shownDeferred; + + ToggleShown(const ToggleShown &other); // NOT COPYABLE + ToggleShown &operator=(const ToggleShown &other); // NOT ASSIGNABLE + + static gboolean notify_visible(ui::Widget widget, gpointer dummy, ToggleShown *self); + + static gboolean destroy(ui::Widget widget, ToggleShown *self); + +public: + ui::Widget m_widget; + ToggleItem m_item; + + ToggleShown(bool shown) + : m_shownDeferred(shown), m_widget(ui::null), m_item(ActiveCaller(*this)) + { + } + + void update(); + + bool active() const; + + void exportActive(const Callback &importCallback); + + typedef MemberCaller &), &ToggleShown::exportActive> ActiveCaller; + + void set(bool shown); + + void toggle(); + + typedef MemberCaller ToggleCaller; + + void connect(ui::Widget widget); +}; + + +void widget_queue_draw(ui::Widget &widget); + +typedef ReferenceCaller WidgetQueueDrawCaller; + + +void widget_make_default(ui::Widget widget); + +class WidgetFocusPrinter { + const char *m_name; + + static gboolean focus_in(ui::Widget widget, GdkEventFocus *event, WidgetFocusPrinter *self); + + static gboolean focus_out(ui::Widget widget, GdkEventFocus *event, WidgetFocusPrinter *self); + +public: + WidgetFocusPrinter(const char *name) : m_name(name) + { + } + + void connect(ui::Widget widget); +}; + +#endif diff --git a/libs/gtkutil/window.cpp b/libs/gtkutil/window.cpp new file mode 100644 index 0000000..79ba883 --- /dev/null +++ b/libs/gtkutil/window.cpp @@ -0,0 +1,253 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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(ui::Widget w) +{ + if (gpointer_to_int(g_object_get_data(G_OBJECT(w), "was_mapped")) != 0) { + w.show(); + } +} + +inline void CHECK_MINIMIZE(ui::Widget w) +{ + g_object_set_data(G_OBJECT(w), "was_mapped", gint_to_pointer(gtk_widget_get_visible(w))); + w.hide(); +} + +static gboolean main_window_iconified(ui::Widget 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(ui::Widget::from(data)); + } else { + CHECK_RESTORE(ui::Widget::from(data)); + } + } + return FALSE; +} + +unsigned int connect_floating(ui::Window main_window, ui::Window floating) +{ + return main_window.connect("window_state_event", G_CALLBACK(main_window_iconified), floating); +} + +gboolean destroy_disconnect_floating(ui::Window 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(ui::Window floating, GdkEventFocus *event, ui::Window 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(ui::Window floating, ui::Window main_window) +{ + return floating.connect("delete_event", G_CALLBACK(floating_window_delete_present), main_window); +} + +gboolean floating_window_destroy_present(ui::Window floating, ui::Window 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(ui::Window floating, ui::Window main_window) +{ + return floating.connect("destroy", G_CALLBACK(floating_window_destroy_present), main_window); +} + +ui::Window create_floating_window(const char *title, ui::Window parent) +{ + ui::Window window = ui::Window(ui::window_type::TOP); + gtk_window_set_title(window, title); + + if (parent) { + 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))); + window.connect("destroy", G_CALLBACK(destroy_disconnect_floating), parent); + } + + return window; +} + +void destroy_floating_window(ui::Window window) +{ + window.destroy(); +} + +gint window_realize_remove_sysmenu(ui::Widget widget, gpointer data) +{ + gdk_window_set_decorations(gtk_widget_get_window(widget), (GdkWMDecoration) (GDK_DECOR_ALL | GDK_DECOR_MENU)); + return FALSE; +} + +gboolean persistent_floating_window_delete(ui::Window floating, GdkEvent *event, ui::Window main_window) +{ + floating.hide(); + return TRUE; +} + +ui::Window create_persistent_floating_window(const char *title, ui::Window main_window) +{ + auto window = create_floating_window(title, main_window); + + gtk_widget_set_events(window, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); + + connect_floating_window_delete_present(window, main_window); + window.connect("delete_event", G_CALLBACK(persistent_floating_window_delete), 0); + +#if 0 + if ( g_multimon_globals.m_bStartOnPrimMon && g_multimon_globals.m_bNoSysMenuPopups ) { + window.connect( "realize", G_CALLBACK( window_realize_remove_sysmenu ), 0 ); + } +#endif + + return window; +} + +gint window_realize_remove_minmax(ui::Widget widget, gpointer data) +{ + gdk_window_set_decorations(gtk_widget_get_window(widget), + (GdkWMDecoration) (GDK_DECOR_ALL | GDK_DECOR_MINIMIZE | GDK_DECOR_MAXIMIZE)); + return FALSE; +} + +void window_remove_minmax(ui::Window window) +{ + window.connect("realize", G_CALLBACK(window_realize_remove_minmax), 0); +} + + +ui::ScrolledWindow create_scrolled_window(ui::Policy hscrollbar_policy, ui::Policy vscrollbar_policy, int border) +{ + auto scr = ui::ScrolledWindow(ui::New); + scr.show(); + gtk_scrolled_window_set_policy(scr, (GtkPolicyType) hscrollbar_policy, (GtkPolicyType) vscrollbar_policy); + gtk_scrolled_window_set_shadow_type(scr, GTK_SHADOW_IN); + gtk_container_set_border_width(GTK_CONTAINER(scr), border); + return scr; +} + +gboolean window_focus_in_clear_focus_widget(ui::Window widget, GdkEventKey *event, gpointer data) +{ + gtk_window_set_focus(widget, NULL); + return FALSE; +} + +guint window_connect_focus_in_clear_focus_widget(ui::Window window) +{ + return window.connect("focus_in_event", G_CALLBACK(window_focus_in_clear_focus_widget), NULL); +} + +void window_get_position(ui::Window window, WindowPosition &position) +{ + ASSERT_MESSAGE(window, "error saving window position"); + + gtk_window_get_position(window, &position.x, &position.y); + gtk_window_get_size(window, &position.w, &position.h); +} + +void window_set_position(ui::Window 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, 800, 600); +} + +void WindowPosition_String::Import(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 + } +} + +void WindowPosition_String::Export(const WindowPosition &self, const Callback &returnz) +{ + char buffer[64]; + sprintf(buffer, "%d %d %d %d", self.x, self.y, self.w, self.h); + returnz(buffer); +} + +void WindowPositionTracker_String::Import(WindowPositionTracker &self, const char *value) +{ + WindowPosition position; + WindowPosition_String::Import(position, value); + self.setPosition(position); +} + +void +WindowPositionTracker_String::Export(const WindowPositionTracker &self, const Callback &returnz) +{ + WindowPosition_String::Export(self.getPosition(), returnz); +} + +gboolean WindowPositionTracker::configure(ui::Widget widget, GdkEventConfigure *event, WindowPositionTracker *self) +{ + self->m_position = WindowPosition(event->x, event->y, event->width, event->height); + return FALSE; +} + +void WindowPositionTracker::sync(ui::Window window) +{ + window_set_position(window, m_position); +} + +void WindowPositionTracker::connect(ui::Window window) +{ + sync(window); + window.connect("configure_event", G_CALLBACK(configure), this); +} + +const WindowPosition &WindowPositionTracker::getPosition() const +{ + return m_position; +} + +void WindowPositionTracker::setPosition(const WindowPosition &position) +{ + m_position = position; +} diff --git a/libs/gtkutil/window.h b/libs/gtkutil/window.h new file mode 100644 index 0000000..69135f8 --- /dev/null +++ b/libs/gtkutil/window.h @@ -0,0 +1,105 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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" + +gboolean window_focus_in_clear_focus_widget(ui::Window widget, GdkEventKey *event, gpointer data); + +guint window_connect_focus_in_clear_focus_widget(ui::Window window); + +unsigned int connect_floating(ui::Window main_window, ui::Window floating); + +ui::Window create_floating_window(const char *title, ui::Window parent); + +void destroy_floating_window(ui::Window window); + +ui::Window create_persistent_floating_window(const char *title, ui::Window main_window); + +gboolean persistent_floating_window_delete(ui::Window floating, GdkEvent *event, ui::Window main_window); + +void window_remove_minmax(ui::Window window); + +ui::ScrolledWindow create_scrolled_window(ui::Policy hscrollbar_policy, ui::Policy 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); + +void window_get_position(ui::Window window, WindowPosition &position); + +void window_set_position(ui::Window window, const WindowPosition &position); + +struct WindowPosition_String { + + static void Export(const WindowPosition &self, const Callback &returnz); + + static void Import(WindowPosition &self, const char *value); + +}; + +class WindowPositionTracker { + WindowPosition m_position; + + static gboolean configure(ui::Widget widget, GdkEventConfigure *event, WindowPositionTracker *self); + +public: + WindowPositionTracker() + : m_position(c_default_window_pos) + { + } + + void sync(ui::Window window); + + void connect(ui::Window window); + + const WindowPosition &getPosition() const; + +//hack + void setPosition(const WindowPosition &position); +}; + + +struct WindowPositionTracker_String { + static void Export(const WindowPositionTracker &self, const Callback &returnz); + + static void Import(WindowPositionTracker &self, const char *value); +}; + +#endif diff --git a/libs/gtkutil/xorrectangle.cpp b/libs/gtkutil/xorrectangle.cpp new file mode 100644 index 0000000..c638e0e --- /dev/null +++ b/libs/gtkutil/xorrectangle.cpp @@ -0,0 +1,50 @@ +#include "xorrectangle.h" + +#include + +bool XORRectangle::initialised() const +{ + return !!cr; +} + +void XORRectangle::lazy_init() +{ + if (!initialised()) { + cr = gdk_cairo_create(gtk_widget_get_window(m_widget)); + } +} + +void XORRectangle::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); + GtkAllocation allocation; + gtk_widget_get_allocation(m_widget, &allocation); + cairo_rectangle(cr, x, -(h) - (y - allocation.height), w, h); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE); + cairo_stroke(cr); +} + +XORRectangle::XORRectangle(ui::GLArea widget) : m_widget(widget), cr(0) +{ +} + +XORRectangle::~XORRectangle() +{ + if (initialised()) { + cairo_destroy(cr); + } +} + +void XORRectangle::set(rectangle_t rectangle) +{ + if (gtk_widget_get_realized(m_widget)) { + lazy_init(); + draw(); + m_rectangle = rectangle; + draw(); + } +} diff --git a/libs/gtkutil/xorrectangle.h b/libs/gtkutil/xorrectangle.h new file mode 100644 index 0000000..eab7bf1 --- /dev/null +++ b/libs/gtkutil/xorrectangle.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_XORRECTANGLE_H ) +#define INCLUDED_XORRECTANGLE_H + +#include +#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; + + ui::GLArea m_widget; + cairo_t *cr; + + bool initialised() const; + + void lazy_init(); + + void draw() const; + +public: + XORRectangle(ui::GLArea widget); + + ~XORRectangle(); + + void set(rectangle_t rectangle); +}; + + +#endif diff --git a/libs/imagelib.h b/libs/imagelib.h new file mode 100644 index 0000000..c3e9c1c --- /dev/null +++ b/libs/imagelib.h @@ -0,0 +1,134 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/instancelib.h new file mode 100644 index 0000000..af9dd2b --- /dev/null +++ b/libs/instancelib.h @@ -0,0 +1,168 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 InstanceSetEvaluateTransform +{ +public: +static void apply( InstanceSet& instances ){ + InstanceSet_forEach(instances, [&](scene::Instance &instance) { + InstanceTypeCast::cast(instance)->evaluateTransform(); + }); +} +typedef ReferenceCaller::apply> Caller; +}; + +#endif diff --git a/libs/l_net/CMakeLists.txt b/libs/l_net/CMakeLists.txt new file mode 100644 index 0000000..a0a19a8 --- /dev/null +++ b/libs/l_net/CMakeLists.txt @@ -0,0 +1,14 @@ +set(L_NETLIST + l_net.c l_net.h + ) +if (WIN32) + list(APPEND L_NETLIST l_net_wins.c l_net_wins.h) +else () + list(APPEND L_NETLIST l_net_berkley.c) +endif () + +add_library(l_net ${L_NETLIST}) + +if (WIN32) + target_link_libraries(l_net PRIVATE ws2_32) +endif () diff --git a/libs/l_net/l_net.c b/libs/l_net/l_net.c new file mode 100644 index 0000000..1872722 --- /dev/null +++ b/libs/l_net/l_net.c @@ -0,0 +1,581 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//==================================================================== +// +// Name: l_net.c +// Function: - +// Programmer: MrElusive +// Last update: - +// Tab size: 3 +// Notes: +//==================================================================== + +#include "globaldefs.h" +#include +#include +#include +#include +#include "l_net.h" +#include "l_net_wins.h" + +#if GDEF_DEBUG +void WinPrint( const char *str, ... ){ + va_list argptr; + char text[4096]; + + va_start( argptr,str ); + vsprintf( text, str, argptr ); + va_end( argptr ); + + printf( "%s", text ); +} +#else +void WinPrint( const 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 ){ +#if GDEF_OS_WINDOWS + 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 +#if GDEF_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 *) malloc( 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 ){ + free( 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( const 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 + memcpy( &msg->data[msg->size], string, strlen( string ) + 1 ); + 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->read + 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 ( (size_t) 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 0000000..c807091 --- /dev/null +++ b/libs/l_net/l_net.h @@ -0,0 +1,123 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +#include "bytebool.h" + +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; + +void WinPrint( const char *format, ... ); + +//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( const 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 +const char *WINS_ErrorMessage( int error ); + +#ifdef __cplusplus +} +#endif diff --git a/libs/l_net/l_net_berkley.c b/libs/l_net/l_net_berkley.c new file mode 100644 index 0000000..f53f37e --- /dev/null +++ b/libs/l_net/l_net_berkley.c @@ -0,0 +1,748 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//=========================================================================== +// +// 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 +#include +#include +#include +#include +const int SOCKET_ERROR = -1; +const int INVALID_SOCKET = -1; + +#define WinError WinPrint + +#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]; + +const int DEFAULTnet_hostport = 26000; + +const int 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: - +//=========================================================================== +const 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; +/* + 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 ); + if(local && local->h_addr_list && local->h_addr_list[0]) + myAddr = *(int *)local->h_addr_list[0]; + else + myAddr = inet_addr("127.0.0.1"); + + // 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 ){ + socklen_t 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 ){ + socklen_t 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; + ret = 0; + + 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 ){ + socklen_t 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 0000000..a4a3378 --- /dev/null +++ b/libs/l_net/l_net_wins.c @@ -0,0 +1,761 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +typedef struct tag_error_struct +{ + int errnum; + LPSTR errstr; +} ERROR_STRUCT; + +#define NET_NAMELEN 64 + +char my_tcpip_address[NET_NAMELEN]; + +const int DEFAULTnet_hostport = 26000; + +const int 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} +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +const 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 ); + if(local && local->h_addr_list && local->h_addr_list[0]) + myAddr = *(int *)local->h_addr_list[0]; + else + myAddr = inet_addr("127.0.0.1"); + + // 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 0000000..4ee5803 --- /dev/null +++ b/libs/l_net/l_net_wins.h @@ -0,0 +1,54 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//=========================================================================== +// +// Name: l_net_wins.h +// Function: WinSock +// Programmer: MrElusive +// Last update: TTimo: cross-platform version, l_net library +// Tab Size: 3 +// Notes: +//=========================================================================== + +#include "bytebool.h" + +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/maplib.h b/libs/maplib.h new file mode 100644 index 0000000..474ce4b --- /dev/null +++ b/libs/maplib.h @@ -0,0 +1,238 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/CMakeLists.txt b/libs/math/CMakeLists.txt new file mode 100644 index 0000000..6cfedbe --- /dev/null +++ b/libs/math/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(math + _.cpp + aabb.h + curve.h + frustum.h + line.h + matrix.h + pi.h + plane.h + quaternion.h + vector.h + ) diff --git a/libs/math/_.cpp b/libs/math/_.cpp new file mode 100644 index 0000000..e69de29 diff --git a/libs/math/aabb.h b/libs/math/aabb.h new file mode 100644 index 0000000..917850f --- /dev/null +++ b/libs/math/aabb.h @@ -0,0 +1,280 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 ); + } +} + +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.h b/libs/math/curve.h new file mode 100644 index 0000000..1ba7b5e --- /dev/null +++ b/libs/math/curve.h @@ -0,0 +1,252 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..13f6bfe --- /dev/null +++ b/libs/math/expression.cpp @@ -0,0 +1,209 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 0000000..0b995e3 --- /dev/null +++ b/libs/math/expression.h @@ -0,0 +1,552 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/math/frustum.h new file mode 100644 index 0000000..08990ae --- /dev/null +++ b/libs/math/frustum.h @@ -0,0 +1,607 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/math/line.h new file mode 100644 index 0000000..cf3bec5 --- /dev/null +++ b/libs/math/line.h @@ -0,0 +1,138 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/math/matrix.h new file mode 100644 index 0000000..95ecfac --- /dev/null +++ b/libs/math/matrix.h @@ -0,0 +1,1194 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/math/pi.h new file mode 100644 index 0000000..a9f368c --- /dev/null +++ b/libs/math/pi.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_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.0; +const double c_2pi = 2.0 * c_pi; +const double c_inv_2pi = 1.0 / 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.h b/libs/math/plane.h new file mode 100644 index 0000000..4027164 --- /dev/null +++ b/libs/math/plane.h @@ -0,0 +1,136 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/math/quaternion.h new file mode 100644 index 0000000..24c6861 --- /dev/null +++ b/libs/math/quaternion.h @@ -0,0 +1,301 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 ){ + Quaternion out; + Matrix4 transposed = matrix4_transposed( matrix4 ); + + /* the monkeys added 1.0 to this for some reason. hint: it's WRONG - eukara */ + double trace = transposed[0] + transposed[5] + transposed[10] + 1.0f; + + if ( trace > 0.0 ) { + 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.h b/libs/math/vector.h new file mode 100644 index 0000000..b4fd92c --- /dev/null +++ b/libs/math/vector.h @@ -0,0 +1,709 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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" +#include "globaldefs.h" + +#if GDEF_COMPILER_MSVC + +inline int lrint( double flt ){ + int i; + + _asm + { + fld flt + fistp i + }; + + return i; +} + +inline __int64 llrint( double f ){ + return static_cast<__int64>( f + 0.5 ); +} + +#elif GDEF_OS_BSD + +/*inline long lrint( double f ){ + return static_cast( f + 0.5 ); +}*/ + +/*inline long long llrint( double f ){ + return static_cast( f + 0.5 ); +}*/ + +#elif GDEF_COMPILER_GNU + +// 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); + if ( snap == 0 ) { + return f; + } + return Element( llrint( f / snap ) * snap ); // llrint has more significant bits +} + +/// \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_mid( const BasicVector4& begin, const BasicVector4& end ){ + return vector4_scaled( vector4_added( begin, end ), 0.5 ); +} + +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 0000000..64f3f6e --- /dev/null +++ b/libs/mathlib.h @@ -0,0 +1,483 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 +#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]; + +// Smallest positive value for vec_t such that 1.0 + VEC_SMALLEST_EPSILON_AROUND_ONE != 1.0. +// In the case of 32 bit floats (which is almost certainly the case), it's 0.00000011921. +// Don't forget that your epsilons should depend on the possible range of values, +// because for example adding VEC_SMALLEST_EPSILON_AROUND_ONE to 1024.0 will have no effect. +#define VEC_SMALLEST_EPSILON_AROUND_ONE FLT_EPSILON + +#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 RGBTOGRAY( x ) ( (float)( ( x )[0] ) * 0.2989f + (float)( ( x )[1] ) * 0.5870f + (float)( ( x )[2] ) * 0.1140f ) + +#define Q_rint( in ) ( (vec_t)floor( in + 0.5 ) ) + +qboolean VectorCompare( const vec3_t v1, const vec3_t v2 ); + +qboolean VectorIsOnAxis( vec3_t v ); +qboolean VectorIsOnAxialPlane( vec3_t v ); + +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 ); +// I need this define in order to test some of the regression tests from time to time. +// This define affect the precision of VectorNormalize() function only. +#define MATHLIB_VECTOR_NORMALIZE_PRECISION_FIX 1 +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 ); +/*! m4_det */ +float m4_det( m4x4_t mr ); +/*! 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 ); + + + +//////////////////////////////////////////////////////////////////////////////// +// Below is double-precision math stuff. This was initially needed by the new +// "base winding" code in q3map2 brush processing in order to fix the famous +// "disappearing triangles" issue. These definitions can be used wherever extra +// precision is needed. +//////////////////////////////////////////////////////////////////////////////// + +typedef double vec_accu_t; +typedef vec_accu_t vec3_accu_t[3]; + +// Smallest positive value for vec_accu_t such that 1.0 + VEC_ACCU_SMALLEST_EPSILON_AROUND_ONE != 1.0. +// In the case of 64 bit doubles (which is almost certainly the case), it's 0.00000000000000022204. +// Don't forget that your epsilons should depend on the possible range of values, +// because for example adding VEC_ACCU_SMALLEST_EPSILON_AROUND_ONE to 1024.0 will have no effect. +#define VEC_ACCU_SMALLEST_EPSILON_AROUND_ONE DBL_EPSILON + +vec_accu_t VectorLengthAccu( const vec3_accu_t v ); + +// I have a feeling it may be safer to break these #define functions out into actual functions +// in order to avoid accidental loss of precision. For example, say you call +// VectorScaleAccu(vec3_t, vec_t, vec3_accu_t). The scale would take place in 32 bit land +// and the result would be cast to 64 bit, which would cause total loss of precision when +// scaling by a large factor. +//#define DotProductAccu(x, y) ((x)[0] * (y)[0] + (x)[1] * (y)[1] + (x)[2] * (y)[2]) +//#define VectorSubtractAccu(a, b, c) ((c)[0] = (a)[0] - (b)[0], (c)[1] = (a)[1] - (b)[1], (c)[2] = (a)[2] - (b)[2]) +//#define VectorAddAccu(a, b, c) ((c)[0] = (a)[0] + (b)[0], (c)[1] = (a)[1] + (b)[1], (c)[2] = (a)[2] + (b)[2]) +//#define VectorCopyAccu(a, b) ((b)[0] = (a)[0], (b)[1] = (a)[1], (b)[2] = (a)[2]) +//#define VectorScaleAccu(a, b, c) ((c)[0] = (b) * (a)[0], (c)[1] = (b) * (a)[1], (c)[2] = (b) * (a)[2]) +//#define CrossProductAccu(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 Q_rintAccu(in) ((vec_accu_t) floor(in + 0.5)) + +vec_accu_t DotProductAccu( const vec3_accu_t a, const vec3_accu_t b ); +void VectorSubtractAccu( const vec3_accu_t a, const vec3_accu_t b, vec3_accu_t out ); +void VectorAddAccu( const vec3_accu_t a, const vec3_accu_t b, vec3_accu_t out ); +void VectorCopyAccu( const vec3_accu_t in, vec3_accu_t out ); +void VectorScaleAccu( const vec3_accu_t in, vec_accu_t scaleFactor, vec3_accu_t out ); +void CrossProductAccu( const vec3_accu_t a, const vec3_accu_t b, vec3_accu_t out ); +vec_accu_t Q_rintAccu( vec_accu_t val ); + +void VectorCopyAccuToRegular( const vec3_accu_t in, vec3_t out ); +void VectorCopyRegularToAccu( const vec3_t in, vec3_accu_t out ); +vec_accu_t VectorNormalizeAccu( const vec3_accu_t in, vec3_accu_t out ); + +#ifdef __cplusplus +} +#endif + +#endif /* __MATHLIB__ */ diff --git a/libs/mathlib/CMakeLists.txt b/libs/mathlib/CMakeLists.txt new file mode 100644 index 0000000..5682a5e --- /dev/null +++ b/libs/mathlib/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(mathlib + bbox.c + line.c + m4x4.c + mathlib.c ../mathlib.h + ray.c + ) diff --git a/libs/mathlib/bbox.c b/libs/mathlib/bbox.c new file mode 100644 index 0000000..fe5646d --- /dev/null +++ b/libs/mathlib/bbox.c @@ -0,0 +1,456 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 + */ + +const int NUMDIM = 3; +const int RIGHT = 0; +const int LEFT = 1; +const int 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; i < NUMDIM; i++ ) + { + if ( origin[i] < ( aabb->origin[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 0000000..a50337f --- /dev/null +++ b/libs/mathlib/line.c @@ -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 + */ + +#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 0000000..45e421f --- /dev/null +++ b/libs/mathlib/m4x4.c @@ -0,0 +1,1848 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 < rcount; ++i ) + { + p1 = ( i + 1 != rcount ) ? p0 + 4 : rptr; + b1 = CLIP_X_LT_W( p1 ); + if ( b0 ^ b1 ) { + wptr[0] = p1[0] - p0[0]; + wptr[1] = p1[1] - p0[1]; + wptr[2] = p1[2] - p0[2]; + wptr[3] = p1[3] - p0[3]; + + scale = ( p0[0] - p0[3] ) / ( wptr[3] - wptr[0] ); + + wptr[0] = (vec_t)( p0[0] + scale * ( wptr[0] ) ); + wptr[1] = (vec_t)( p0[1] + scale * ( wptr[1] ) ); + wptr[2] = (vec_t)( p0[2] + scale * ( wptr[2] ) ); + wptr[3] = (vec_t)( p0[3] + scale * ( wptr[3] ) ); + + wptr += 4; + ++wcount; + } + + if ( b1 ) { + wptr[0] = p1[0]; + wptr[1] = p1[1]; + wptr[2] = p1[2]; + wptr[3] = p1[3]; + + wptr += 4; + ++wcount; + } + + p0 = p1; + b0 = b1; + } + + rcount = wcount; + wcount = 0; + rptr = buffer[0]; + wptr = clipped[0]; + p0 = rptr; + b0 = CLIP_X_GT_W( p0 ); + + for ( i = 0; i < rcount; ++i ) + { + p1 = ( i + 1 != rcount ) ? p0 + 4 : rptr; + b1 = CLIP_X_GT_W( p1 ); + if ( b0 ^ b1 ) { + wptr[0] = p1[0] - p0[0]; + wptr[1] = p1[1] - p0[1]; + wptr[2] = p1[2] - p0[2]; + wptr[3] = p1[3] - p0[3]; + + scale = ( p0[0] + p0[3] ) / ( -wptr[3] - wptr[0] ); + + wptr[0] = (vec_t)( p0[0] + scale * ( wptr[0] ) ); + wptr[1] = (vec_t)( p0[1] + scale * ( wptr[1] ) ); + wptr[2] = (vec_t)( p0[2] + scale * ( wptr[2] ) ); + wptr[3] = (vec_t)( p0[3] + scale * ( wptr[3] ) ); + + wptr += 4; + ++wcount; + } + + if ( b1 ) { + wptr[0] = p1[0]; + wptr[1] = p1[1]; + wptr[2] = p1[2]; + wptr[3] = p1[3]; + + wptr += 4; + ++wcount; + } + + p0 = p1; + b0 = b1; + } + + rcount = wcount; + wcount = 0; + rptr = clipped[0]; + wptr = buffer[0]; + p0 = rptr; + b0 = CLIP_Y_LT_W( p0 ); + + for ( i = 0; i < rcount; ++i ) + { + p1 = ( i + 1 != rcount ) ? p0 + 4 : rptr; + b1 = CLIP_Y_LT_W( p1 ); + if ( b0 ^ b1 ) { + wptr[0] = p1[0] - p0[0]; + wptr[1] = p1[1] - p0[1]; + wptr[2] = p1[2] - p0[2]; + wptr[3] = p1[3] - p0[3]; + + scale = ( p0[1] - p0[3] ) / ( wptr[3] - wptr[1] ); + + wptr[0] = (vec_t)( p0[0] + scale * ( wptr[0] ) ); + wptr[1] = (vec_t)( p0[1] + scale * ( wptr[1] ) ); + wptr[2] = (vec_t)( p0[2] + scale * ( wptr[2] ) ); + wptr[3] = (vec_t)( p0[3] + scale * ( wptr[3] ) ); + + wptr += 4; + ++wcount; + } + + if ( b1 ) { + wptr[0] = p1[0]; + wptr[1] = p1[1]; + wptr[2] = p1[2]; + wptr[3] = p1[3]; + + wptr += 4; + ++wcount; + } + + p0 = p1; + b0 = b1; + } + + rcount = wcount; + wcount = 0; + rptr = buffer[0]; + wptr = clipped[0]; + p0 = rptr; + b0 = CLIP_Y_GT_W( p0 ); + + for ( i = 0; i < rcount; ++i ) + { + p1 = ( i + 1 != rcount ) ? p0 + 4 : rptr; + b1 = CLIP_Y_GT_W( p1 ); + if ( b0 ^ b1 ) { + wptr[0] = p1[0] - p0[0]; + wptr[1] = p1[1] - p0[1]; + wptr[2] = p1[2] - p0[2]; + wptr[3] = p1[3] - p0[3]; + + scale = ( p0[1] + p0[3] ) / ( -wptr[3] - wptr[1] ); + + wptr[0] = (vec_t)( p0[0] + scale * ( wptr[0] ) ); + wptr[1] = (vec_t)( p0[1] + scale * ( wptr[1] ) ); + wptr[2] = (vec_t)( p0[2] + scale * ( wptr[2] ) ); + wptr[3] = (vec_t)( p0[3] + scale * ( wptr[3] ) ); + + wptr += 4; + ++wcount; + } + + if ( b1 ) { + wptr[0] = p1[0]; + wptr[1] = p1[1]; + wptr[2] = p1[2]; + wptr[3] = p1[3]; + + wptr += 4; + ++wcount; + } + + p0 = p1; + b0 = b1; + } + + rcount = wcount; + wcount = 0; + rptr = clipped[0]; + wptr = buffer[0]; + p0 = rptr; + b0 = CLIP_Z_LT_W( p0 ); + + for ( i = 0; i < rcount; ++i ) + { + p1 = ( i + 1 != rcount ) ? p0 + 4 : rptr; + b1 = CLIP_Z_LT_W( p1 ); + if ( b0 ^ b1 ) { + wptr[0] = p1[0] - p0[0]; + wptr[1] = p1[1] - p0[1]; + wptr[2] = p1[2] - p0[2]; + wptr[3] = p1[3] - p0[3]; + + scale = ( p0[2] - p0[3] ) / ( wptr[3] - wptr[2] ); + + wptr[0] = (vec_t)( p0[0] + scale * ( wptr[0] ) ); + wptr[1] = (vec_t)( p0[1] + scale * ( wptr[1] ) ); + wptr[2] = (vec_t)( p0[2] + scale * ( wptr[2] ) ); + wptr[3] = (vec_t)( p0[3] + scale * ( wptr[3] ) ); + + wptr += 4; + ++wcount; + } + + if ( b1 ) { + wptr[0] = p1[0]; + wptr[1] = p1[1]; + wptr[2] = p1[2]; + wptr[3] = p1[3]; + + wptr += 4; + ++wcount; + } + + p0 = p1; + b0 = b1; + } + + rcount = wcount; + wcount = 0; + rptr = buffer[0]; + wptr = clipped[0]; + p0 = rptr; + b0 = CLIP_Z_GT_W( p0 ); + + for ( i = 0; i < rcount; ++i ) + { + p1 = ( i + 1 != rcount ) ? p0 + 4 : rptr; + b1 = CLIP_Z_GT_W( p1 ); + if ( b0 ^ b1 ) { + wptr[0] = p1[0] - p0[0]; + wptr[1] = p1[1] - p0[1]; + wptr[2] = p1[2] - p0[2]; + wptr[3] = p1[3] - p0[3]; + + scale = ( p0[2] + p0[3] ) / ( -wptr[3] - wptr[2] ); + + wptr[0] = (vec_t)( p0[0] + scale * ( wptr[0] ) ); + wptr[1] = (vec_t)( p0[1] + scale * ( wptr[1] ) ); + wptr[2] = (vec_t)( p0[2] + scale * ( wptr[2] ) ); + wptr[3] = (vec_t)( p0[3] + scale * ( wptr[3] ) ); + + wptr += 4; + ++wcount; + } + + if ( b1 ) { + wptr[0] = p1[0]; + wptr[1] = p1[1]; + wptr[2] = p1[2]; + wptr[3] = p1[3]; + + wptr += 4; + ++wcount; + } + + p0 = p1; + b0 = b1; + } + + return wcount; +} + +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] ){ + 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; + + m4x4_transform_vec4( matrix, clipped[0] ); + m4x4_transform_vec4( matrix, clipped[1] ); + m4x4_transform_vec4( matrix, clipped[2] ); + + return homogenous_clip_triangle( clipped ); +} + +unsigned int homogenous_clip_line( vec4_t clipped[2] ){ + vec4_t clip; + double scale; + const vec_t* const p0 = clipped[0]; + const vec_t* const p1 = clipped[1]; + + // early out + { + clipmask_t mask0 = homogenous_clip_point( clipped[0] ); + clipmask_t mask1 = homogenous_clip_point( clipped[1] ); + + if ( ( mask0 | mask1 ) == CLIP_PASS ) { // both points passed all planes + return 2; + } + + if ( mask0 & mask1 ) { // both points failed any one plane + return 0; + } + } + + { + const unsigned int index = CLIP_X_LT_W( p0 ); + if ( index ^ CLIP_X_LT_W( p1 ) ) { + clip[0] = p1[0] - p0[0]; + clip[1] = p1[1] - p0[1]; + clip[2] = p1[2] - p0[2]; + clip[3] = p1[3] - p0[3]; + + scale = ( p0[0] - p0[3] ) / ( clip[3] - clip[0] ); + + clip[0] = (vec_t)( p0[0] + scale * ( clip[0] ) ); + clip[1] = (vec_t)( p0[1] + scale * ( clip[1] ) ); + clip[2] = (vec_t)( p0[2] + scale * ( clip[2] ) ); + clip[3] = (vec_t)( p0[3] + scale * ( clip[3] ) ); + + clipped[index][0] = clip[0]; + clipped[index][1] = clip[1]; + clipped[index][2] = clip[2]; + clipped[index][3] = clip[3]; + } + else if ( index == 0 ) { + return 0; + } + } + + { + const unsigned int index = CLIP_X_GT_W( p0 ); + if ( index ^ CLIP_X_GT_W( p1 ) ) { + clip[0] = p1[0] - p0[0]; + clip[1] = p1[1] - p0[1]; + clip[2] = p1[2] - p0[2]; + clip[3] = p1[3] - p0[3]; + + scale = ( p0[0] + p0[3] ) / ( -clip[3] - clip[0] ); + + clip[0] = (vec_t)( p0[0] + scale * ( clip[0] ) ); + clip[1] = (vec_t)( p0[1] + scale * ( clip[1] ) ); + clip[2] = (vec_t)( p0[2] + scale * ( clip[2] ) ); + clip[3] = (vec_t)( p0[3] + scale * ( clip[3] ) ); + + clipped[index][0] = clip[0]; + clipped[index][1] = clip[1]; + clipped[index][2] = clip[2]; + clipped[index][3] = clip[3]; + } + else if ( index == 0 ) { + return 0; + } + } + + { + const unsigned int index = CLIP_Y_LT_W( p0 ); + if ( index ^ CLIP_Y_LT_W( p1 ) ) { + clip[0] = p1[0] - p0[0]; + clip[1] = p1[1] - p0[1]; + clip[2] = p1[2] - p0[2]; + clip[3] = p1[3] - p0[3]; + + scale = ( p0[1] - p0[3] ) / ( clip[3] - clip[1] ); + + clip[0] = (vec_t)( p0[0] + scale * ( clip[0] ) ); + clip[1] = (vec_t)( p0[1] + scale * ( clip[1] ) ); + clip[2] = (vec_t)( p0[2] + scale * ( clip[2] ) ); + clip[3] = (vec_t)( p0[3] + scale * ( clip[3] ) ); + + clipped[index][0] = clip[0]; + clipped[index][1] = clip[1]; + clipped[index][2] = clip[2]; + clipped[index][3] = clip[3]; + } + else if ( index == 0 ) { + return 0; + } + } + + { + const unsigned int index = CLIP_Y_GT_W( p0 ); + if ( index ^ CLIP_Y_GT_W( p1 ) ) { + clip[0] = p1[0] - p0[0]; + clip[1] = p1[1] - p0[1]; + clip[2] = p1[2] - p0[2]; + clip[3] = p1[3] - p0[3]; + + scale = ( p0[1] + p0[3] ) / ( -clip[3] - clip[1] ); + + clip[0] = (vec_t)( p0[0] + scale * ( clip[0] ) ); + clip[1] = (vec_t)( p0[1] + scale * ( clip[1] ) ); + clip[2] = (vec_t)( p0[2] + scale * ( clip[2] ) ); + clip[3] = (vec_t)( p0[3] + scale * ( clip[3] ) ); + + clipped[index][0] = clip[0]; + clipped[index][1] = clip[1]; + clipped[index][2] = clip[2]; + clipped[index][3] = clip[3]; + } + else if ( index == 0 ) { + return 0; + } + } + + { + const unsigned int index = CLIP_Z_LT_W( p0 ); + if ( index ^ CLIP_Z_LT_W( p1 ) ) { + clip[0] = p1[0] - p0[0]; + clip[1] = p1[1] - p0[1]; + clip[2] = p1[2] - p0[2]; + clip[3] = p1[3] - p0[3]; + + scale = ( p0[2] - p0[3] ) / ( clip[3] - clip[2] ); + + clip[0] = (vec_t)( p0[0] + scale * ( clip[0] ) ); + clip[1] = (vec_t)( p0[1] + scale * ( clip[1] ) ); + clip[2] = (vec_t)( p0[2] + scale * ( clip[2] ) ); + clip[3] = (vec_t)( p0[3] + scale * ( clip[3] ) ); + + clipped[index][0] = clip[0]; + clipped[index][1] = clip[1]; + clipped[index][2] = clip[2]; + clipped[index][3] = clip[3]; + } + else if ( index == 0 ) { + return 0; + } + } + + { + const unsigned int index = CLIP_Z_GT_W( p0 ); + if ( index ^ CLIP_Z_GT_W( p1 ) ) { + clip[0] = p1[0] - p0[0]; + clip[1] = p1[1] - p0[1]; + clip[2] = p1[2] - p0[2]; + clip[3] = p1[3] - p0[3]; + + scale = ( p0[2] + p0[3] ) / ( -clip[3] - clip[2] ); + + clip[0] = (vec_t)( p0[0] + scale * ( clip[0] ) ); + clip[1] = (vec_t)( p0[1] + scale * ( clip[1] ) ); + clip[2] = (vec_t)( p0[2] + scale * ( clip[2] ) ); + clip[3] = (vec_t)( p0[3] + scale * ( clip[3] ) ); + + clipped[index][0] = clip[0]; + clipped[index][1] = clip[1]; + clipped[index][2] = clip[2]; + clipped[index][3] = clip[3]; + } + else if ( index == 0 ) { + return 0; + } + } + + return 2; +} + +unsigned int m4x4_clip_line( const m4x4_t matrix, const vec3_t p0, const vec3_t p1, vec4_t 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; + + m4x4_transform_vec4( matrix, clipped[0] ); + m4x4_transform_vec4( matrix, clipped[1] ); + + return homogenous_clip_line( clipped ); +} + +void m4x4_transpose( m4x4_t matrix ){ + int i, j; + float temp, *p1, *p2; + + for ( i = 1; i < 4; i++ ) { + for ( j = 0; j < i; j++ ) { + p1 = matrix + ( j * 4 + i ); + p2 = matrix + ( i * 4 + j ); + temp = *p1; + *p1 = *p2; + *p2 = temp; + } + } +} + +/* adapted from Graphics Gems 2 + invert a 3d matrix (4x3) */ +int m4x4_orthogonal_invert( m4x4_t matrix ){ + m4x4_t temp; + vec_t* src = temp; + + m4x4_assign( src, matrix ); + + /* Calculate the determinant of upper left 3x3 submatrix and + * determine if the matrix is singular. + */ + { +#if 0 + float pos = 0.0f; + float neg = 0.0f; + float det = src[0] * src[5] * src[10]; + if ( det >= 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; + } + else{ + continue; + } + + for ( tj = 0; tj < 4; tj++ ) + { + if ( tj < j ) { + jdst = tj; + } + else if ( tj > j ) { + jdst = tj - 1; + } + else{ + continue; + } + + 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 + +int matrix_solve_ge( vec_t* matrix, vec_t* aug, vec3_t x ){ + const int N = 3; + int indx[N]; + int c,r; + int i; + int best; + float scale[N]; + float f, pivot; + float ratio; + float* p; + + for ( r = 0; r < N; r++ ) + { + indx[r] = r; + } + + for ( r = 0; r < N; r++ ) + { + p = matrix + r; + scale[r] = 0; + for ( c = 0; c < N; c++, p++ ) + { + if ( fabs( *p ) > scale[r] ) { + scale[r] = (float)fabs( *p ); + } + } + } + + for ( c = 0; c < N; c++ ) + { + pivot = 0; + best = -1; + for ( r = c; r < N; r++ ) + { + f = (float)fabs( matrix[( indx[r] * N ) + c] ) / scale[indx[r]]; + if ( f > 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 < N; r++ ) + { + p = matrix + ( indx[r] * N ); + ratio = p[c] / matrix[( indx[c] * N ) + c]; + + for ( i = c + 1; i < N; i++ ) p[i] -= ratio * matrix[( indx[c] * N ) + i]; + aug[indx[r]] -= ratio * aug[indx[c]]; + } + } + + x[N - 1] = aug[indx[N - 1]] / matrix[( indx[N - 1] * N ) + N - 1]; + for ( r = 1; r >= 0; r-- ) + { + f = aug[indx[r]]; + p = matrix + ( indx[r] * N ); + for ( c = ( r + 1 ); c < N; c++ ) f -= ( p[c] * x[c] ); + x[r] = f / p[r]; + } + return 0; +} + +#ifdef YOU_WANT_IT_TO_BORK +/* Gaussian elimination */ +for ( i = 0; i < 4; i++ ) +{ + for ( j = ( i + 1 ); j < 4; j++ ) + { + ratio = matrix[j][i] / matrix[i][i]; + for ( count = i; count < n; count++ ) { + matrix[j][count] -= ( ratio * matrix[i][count] ); + } + b[j] -= ( ratio * b[i] ); + } +} + +/* Back substitution */ +x[n - 1] = b[n - 1] / matrix[n - 1][n - 1]; +for ( i = ( n - 2 ); i >= 0; i-- ) +{ + temp = b[i]; + for ( j = ( i + 1 ); j < n; j++ ) + { + temp -= ( matrix[i][j] * x[j] ); + } + x[i] = temp / matrix[i][i]; +} +#endif + +int plane_intersect_planes( const vec4_t plane1, const vec4_t plane2, const vec4_t plane3, vec3_t intersection ){ + m3x3_t planes; + vec3_t b; + VectorCopy( plane1, planes + 0 ); + b[0] = plane1[3]; + VectorCopy( plane2, planes + 3 ); + b[1] = plane2[3]; + VectorCopy( plane3, planes + 6 ); + b[2] = plane3[3]; + + return matrix_solve_ge( planes, b, intersection ); +} diff --git a/libs/mathlib/mathlib.c b/libs/mathlib/mathlib.c new file mode 100644 index 0000000..da26a92 --- /dev/null +++ b/libs/mathlib/mathlib.c @@ -0,0 +1,773 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// mathlib.c -- math primitives +#include "mathlib.h" +// we use memcpy and memset +#include + +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, }; + +/* + ================ + VectorIsOnAxis + ================ + */ +qboolean VectorIsOnAxis( vec3_t v ){ + int i, zeroComponentCount; + + zeroComponentCount = 0; + for ( i = 0; i < 3; i++ ) + { + if ( v[i] == 0.0 ) { + zeroComponentCount++; + } + } + + if ( zeroComponentCount > 1 ) { + // The zero vector will be on axis. + return qtrue; + } + + return qfalse; +} + +/* + ================ + VectorIsOnAxialPlane + ================ + */ +qboolean VectorIsOnAxialPlane( vec3_t v ){ + int i; + + for ( i = 0; i < 3; i++ ) + { + if ( v[i] == 0.0 ) { + // The zero vector will be on axial plane. + return qtrue; + } + } + + return qfalse; +} + +/* + ================ + 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 ) { + +#if MATHLIB_VECTOR_NORMALIZE_PRECISION_FIX + + // The sqrt() function takes double as an input and returns double as an + // output according the the man pages on Debian and on FreeBSD. Therefore, + // I don't see a reason why using a double outright (instead of using the + // vec_accu_t alias for example) could possibly be frowned upon. + + double x, y, z, length; + + x = (double) in[0]; + y = (double) in[1]; + z = (double) in[2]; + + length = sqrt( ( x * x ) + ( y * y ) + ( z * z ) ); + if ( length == 0 ) { + VectorClear( out ); + return 0; + } + + out[0] = (vec_t) ( x / length ); + out[1] = (vec_t) ( y / length ); + out[2] = (vec_t) ( z / length ); + + return (vec_t) length; + +#else + + 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; + +#endif + +} + +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 +const int INVALID_BOUNDS = 99999; +void ClearBounds( vec3_t mins, vec3_t maxs ){ + mins[0] = mins[1] = mins[2] = +INVALID_BOUNDS; + maxs[0] = maxs[1] = maxs[2] = -INVALID_BOUNDS; +} + +void AddPointToBounds( vec3_t v, vec3_t mins, vec3_t maxs ){ + int i; + vec_t val; + + if ( mins[0] == +INVALID_BOUNDS ) { + if ( maxs[0] == -INVALID_BOUNDS ) { + VectorCopy( v, mins ); + VectorCopy( v, maxs ); + } + } + + 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]; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Below is double-precision math stuff. This was initially needed by the new +// "base winding" code in q3map2 brush processing in order to fix the famous +// "disappearing triangles" issue. These definitions can be used wherever extra +// precision is needed. +//////////////////////////////////////////////////////////////////////////////// + +/* + ================= + VectorLengthAccu + ================= + */ +vec_accu_t VectorLengthAccu( const vec3_accu_t v ){ + return (vec_accu_t) sqrt( ( v[0] * v[0] ) + ( v[1] * v[1] ) + ( v[2] * v[2] ) ); +} + +/* + ================= + DotProductAccu + ================= + */ +vec_accu_t DotProductAccu( const vec3_accu_t a, const vec3_accu_t b ){ + return ( a[0] * b[0] ) + ( a[1] * b[1] ) + ( a[2] * b[2] ); +} + +/* + ================= + VectorSubtractAccu + ================= + */ +void VectorSubtractAccu( const vec3_accu_t a, const vec3_accu_t b, vec3_accu_t out ){ + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; +} + +/* + ================= + VectorAddAccu + ================= + */ +void VectorAddAccu( const vec3_accu_t a, const vec3_accu_t b, vec3_accu_t out ){ + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; +} + +/* + ================= + VectorCopyAccu + ================= + */ +void VectorCopyAccu( const vec3_accu_t in, vec3_accu_t out ){ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +/* + ================= + VectorScaleAccu + ================= + */ +void VectorScaleAccu( const vec3_accu_t in, vec_accu_t scaleFactor, vec3_accu_t out ){ + out[0] = in[0] * scaleFactor; + out[1] = in[1] * scaleFactor; + out[2] = in[2] * scaleFactor; +} + +/* + ================= + CrossProductAccu + ================= + */ +void CrossProductAccu( const vec3_accu_t a, const vec3_accu_t b, vec3_accu_t out ){ + out[0] = ( a[1] * b[2] ) - ( a[2] * b[1] ); + out[1] = ( a[2] * b[0] ) - ( a[0] * b[2] ); + out[2] = ( a[0] * b[1] ) - ( a[1] * b[0] ); +} + +/* + ================= + Q_rintAccu + ================= + */ +vec_accu_t Q_rintAccu( vec_accu_t val ){ + return (vec_accu_t) floor( val + 0.5 ); +} + +/* + ================= + VectorCopyAccuToRegular + ================= + */ +void VectorCopyAccuToRegular( const vec3_accu_t in, vec3_t out ){ + out[0] = (vec_t) in[0]; + out[1] = (vec_t) in[1]; + out[2] = (vec_t) in[2]; +} + +/* + ================= + VectorCopyRegularToAccu + ================= + */ +void VectorCopyRegularToAccu( const vec3_t in, vec3_accu_t out ){ + out[0] = (vec_accu_t) in[0]; + out[1] = (vec_accu_t) in[1]; + out[2] = (vec_accu_t) in[2]; +} + +/* + ================= + VectorNormalizeAccu + ================= + */ +vec_accu_t VectorNormalizeAccu( const vec3_accu_t in, vec3_accu_t out ){ + // The sqrt() function takes double as an input and returns double as an + // output according the the man pages on Debian and on FreeBSD. Therefore, + // I don't see a reason why using a double outright (instead of using the + // vec_accu_t alias for example) could possibly be frowned upon. + + vec_accu_t length; + + length = (vec_accu_t) sqrt( ( in[0] * in[0] ) + ( in[1] * in[1] ) + ( in[2] * in[2] ) ); + if ( length == 0 ) { + VectorClear( out ); + return 0; + } + + out[0] = in[0] / length; + out[1] = in[1] / length; + out[2] = in[2] / length; + + return length; +} diff --git a/libs/mathlib/ray.c b/libs/mathlib/ray.c new file mode 100644 index 0000000..36a2616 --- /dev/null +++ b/libs/mathlib/ray.c @@ -0,0 +1,143 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 + +const double 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 0000000..c54f260 --- /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 0000000..897e524 --- /dev/null +++ b/libs/md5lib/md5lib.c @@ -0,0 +1,388 @@ +/* + 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 "globaldefs.h" +#include + +#if GDEF_ARCH_ENDIAN_BIG +#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 */ +#define BYTE_ORDER ( ARCH_IS_BIG_ENDIAN ? 1 : -1 ) + +#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/memory/CMakeLists.txt b/libs/memory/CMakeLists.txt new file mode 100644 index 0000000..1c34573 --- /dev/null +++ b/libs/memory/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(memory + allocator.cpp allocator.h + ) diff --git a/libs/memory/allocator.cpp b/libs/memory/allocator.cpp new file mode 100644 index 0000000..0e4decf --- /dev/null +++ b/libs/memory/allocator.cpp @@ -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 + */ + +#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 0000000..845ce14 --- /dev/null +++ b/libs/memory/allocator.h @@ -0,0 +1,291 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#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 ) : DefaultAllocator(), m_name( name ){ +} +NamedAllocator( const NamedAllocator& other ) : DefaultAllocator(), m_name( other.m_name ){ +} +template NamedAllocator( const NamedAllocator& other ) : DefaultAllocator(), 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.h b/libs/moduleobservers.h new file mode 100644 index 0000000..1ee91e2 --- /dev/null +++ b/libs/moduleobservers.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/CMakeLists.txt b/libs/modulesystem/CMakeLists.txt new file mode 100644 index 0000000..d27aa63 --- /dev/null +++ b/libs/modulesystem/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(modulesystem + moduleregistry.h + modulesmap.h + singletonmodule.cpp singletonmodule.h + ) diff --git a/libs/modulesystem/moduleregistry.h b/libs/modulesystem/moduleregistry.h new file mode 100644 index 0000000..7cd84f9 --- /dev/null +++ b/libs/modulesystem/moduleregistry.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/modulesystem/modulesmap.h new file mode 100644 index 0000000..a48a083 --- /dev/null +++ b/libs/modulesystem/modulesmap.h @@ -0,0 +1,135 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..207051a --- /dev/null +++ b/libs/modulesystem/singletonmodule.cpp @@ -0,0 +1,46 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 0000000..8dcd88d --- /dev/null +++ b/libs/modulesystem/singletonmodule.h @@ -0,0 +1,131 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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/CMakeLists.txt b/libs/os/CMakeLists.txt new file mode 100644 index 0000000..437b506 --- /dev/null +++ b/libs/os/CMakeLists.txt @@ -0,0 +1,10 @@ +add_library(os + _.cpp + dir.h + file.h + path.h + ) + +find_package(GLIB REQUIRED) +target_include_directories(os PRIVATE ${GLIB_INCLUDE_DIRS}) +target_link_libraries(os PRIVATE ${GLIB_LIBRARIES}) diff --git a/libs/os/_.cpp b/libs/os/_.cpp new file mode 100644 index 0000000..e69de29 diff --git a/libs/os/dir.h b/libs/os/dir.h new file mode 100644 index 0000000..b199284 --- /dev/null +++ b/libs/os/dir.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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.h b/libs/os/file.h new file mode 100644 index 0000000..144627e --- /dev/null +++ b/libs/os/file.h @@ -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 + */ + +#if !defined( INCLUDED_OS_FILE_H ) +#define INCLUDED_OS_FILE_H + +#include "globaldefs.h" + +/// \file +/// \brief OS file-system querying and manipulation. + +#if GDEF_OS_WINDOWS +#define S_ISDIR( mode ) ( mode & _S_IFDIR ) +#include // _access() +#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.h b/libs/os/path.h new file mode 100644 index 0000000..a3db167 --- /dev/null +++ b/libs/os/path.h @@ -0,0 +1,255 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +#include "globaldefs.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 GDEF_OS_WINDOWS +#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 GDEF_OS_WINDOWS + return path[0] == '/' + || ( path[0] != '\0' && path[1] == ':' ); // local drive +#elif GDEF_OS_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 0000000..5c4a8f0 --- /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 \ + const char *fileName, const void *buffer, int bufSize + +#define PM_PARAMS_LOAD \ + const char *fileName, int frameNum, const void *buffer, int bufSize + +#define PM_PARAMS_CANSAVE \ + void + +#define PM_PARAMS_SAVE \ + const 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 )( const 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( const 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, const char *fileName ); + +/* 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, const char *name ); +void PicoSetModelFileName( picoModel_t *model, const 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, const 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, const 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, const char *name, picoIndex_t* smoothingGroup ); + +/* end marker */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/picomodel/CMakeLists.txt b/libs/picomodel/CMakeLists.txt new file mode 100644 index 0000000..b2e8ccc --- /dev/null +++ b/libs/picomodel/CMakeLists.txt @@ -0,0 +1,27 @@ +add_library(picomodel + lwo/clip.c + lwo/envelope.c + lwo/list.c + lwo/lwio.c + lwo/lwo2.c lwo/lwo2.h + lwo/lwob.c + lwo/pntspols.c + lwo/surface.c + lwo/vecmath.c + lwo/vmap.c + + picointernal.c picointernal.h + picomodel.c ../picomodel.h + picomodules.c + pm_3ds.c + pm_ase.c + pm_fm.c pm_fm.h + pm_lwo.c + pm_md2.c + pm_md3.c + pm_mdc.c + pm_ms3d.c + pm_obj.c + pm_terrain.c + pm_iqm.c + ) diff --git a/libs/picomodel/lwo/clip.c b/libs/picomodel/lwo/clip.c new file mode 100644 index 0000000..6a347f5 --- /dev/null +++ b/libs/picomodel/lwo/clip.c @@ -0,0 +1,300 @@ +/* + ====================================================================== + 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 (*)(void *)) lwFreePlugin); + lwListFree(clip->pfilter, (void (*)(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 0000000..27992d4 --- /dev/null +++ b/libs/picomodel/lwo/envelope.c @@ -0,0 +1,641 @@ +/* + ====================================================================== + 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 (*)(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 = NULL; + 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, (int (*)(void *, 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 0000000..d57547d --- /dev/null +++ b/libs/picomodel/lwo/list.c @@ -0,0 +1,100 @@ +/* + ====================================================================== + 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 0000000..e825a34 --- /dev/null +++ b/libs/picomodel/lwo/lwio.c @@ -0,0 +1,472 @@ +/* + ====================================================================== + lwio.c + + Functions for reading basic LWO2 data types. + + Ernie Wright 17 Sep 00 + ====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" +#include +#include "globaldefs.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. + ====================================================================== */ + +const int FLEN_ERROR = INT_MIN; + +static int flen; + +void set_flen( int i ) { flen = i; } + +int get_flen( void ) { return flen; } + + +#if !GDEF_ARCH_ENDIAN_BIG +/* + ===================================================================== + 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( (const char *) 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 0000000..fd367e4 --- /dev/null +++ b/libs/picomodel/lwo/lwo2.c @@ -0,0 +1,365 @@ +/* + ====================================================================== + lwo2.c + + The entry point for loading LightWave object files. + + Ernie Wright 17 Sep 00 + ====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" +#include "globaldefs.h" + +/* disable warnings */ +#if GDEF_COMPILER_MSVC +#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 (*)(void *)) lwFreeVMap); + _pico_free( layer ); + } +} + + +/* + ====================================================================== + lwFreeObject() + + Free memory used by an lwObject. + ====================================================================== */ + +void lwFreeObject( lwObject *object ){ + if ( object ) { + lwListFree(object->layer, (void (*)(void *)) lwFreeLayer); + lwListFree(object->env, (void (*)(void *)) lwFreeEnvelope); + lwListFree(object->clip, (void (*)(void *)) lwFreeClip); + lwListFree(object->surf, (void (*)(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( const char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ){ + lwObject *object; + lwLayer *layer; + lwNode *node; + unsigned int id, formsize, type; + int i, rlen, 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; + } + + /* 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 <= (unsigned int) ( _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( const char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ){ + unsigned int id, 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 0000000..b1dc747 --- /dev/null +++ b/libs/picomodel/lwo/lwo2.h @@ -0,0 +1,653 @@ +/* + ====================================================================== + lwo2.h + + Definitions and typedefs for LWO2 files. + + Ernie Wright 17 Sep 00 + ====================================================================== */ + +#ifndef LWO2_H +#define LWO2_H + +#include "globaldefs.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( const char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); +int lwValidateObject( const 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( const char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); +int lwValidateObject5( const 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 ); + +#if !GDEF_ARCH_ENDIAN_BIG +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 0000000..cae5357 --- /dev/null +++ b/libs/picomodel/lwo/lwob.c @@ -0,0 +1,871 @@ +/* + ====================================================================== + 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" +#include "globaldefs.h" + +/* disable warnings */ +#if GDEF_COMPILER_MSVC +#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)" ) ) != NULL ) { + 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 = NULL; + lwPlugin *shdr = NULL; + 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: + if( tex == NULL ) { + goto Fail; + } + + flags = getU2( fp ); + + i = -1; + + //only one of the three axis bits should be set + if ( flags & 1 ) { + i = 0; + } + if ( flags & 2 ) { + i = 1; + } + if ( flags & 4 ) { + i = 2; + } + if ( i < 0 ) { + goto Fail; + } + 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: + if ( !tex ) { + goto Fail; + } + for ( i = 0; i < 3; i++ ) + tex->tmap.size.val[ i ] = getF4( fp ); + break; + + case ID_TCTR: + if ( !tex ) { + goto Fail; + } + for ( i = 0; i < 3; i++ ) + tex->tmap.center.val[ i ] = getF4( fp ); + break; + + case ID_TFAL: + if ( !tex ) { + goto Fail; + } + for ( i = 0; i < 3; i++ ) + tex->tmap.falloff.val[ i ] = getF4( fp ); + break; + + case ID_TVEL: + if ( !tex ) { + goto Fail; + } + 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 ) { + goto Fail; + } + if ( tex->type == ID_PROC ) { + for ( i = 0; i < 3; i++ ) + tex->param.proc.value[ i ] = getU1( fp ) / 255.0f; + } + break; + + case ID_TVAL: + if ( !tex ) { + goto Fail; + } + tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f; + break; + + case ID_TAMP: + if ( !tex ) { + goto Fail; + } + if ( tex->type == ID_IMAP ) { + tex->param.imap.amplitude.val = getF4( fp ); + } + break; + + case ID_TIMG: + if ( !tex ) { + goto Fail; + } + s = getS0( fp ); + tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips ); + break; + + case ID_TAAS: + if ( !tex ) { + goto Fail; + } + tex->param.imap.aa_strength = getF4( fp ); + tex->param.imap.aas_flags = 1; + break; + + case ID_TREF: + if ( !tex ) { + goto Fail; + } + tex->tmap.ref_object = getbytes( fp, sz ); + break; + + case ID_TOPC: + if ( !tex ) { + goto Fail; + } + tex->opacity.val = getF4( fp ); + break; + + case ID_TFP0: + if ( !tex ) { + goto Fail; + } + if ( tex->type == ID_IMAP ) { + tex->param.imap.wrapw.val = getF4( fp ); + } + break; + + case ID_TFP1: + if ( !tex ) { + goto Fail; + } + 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: + if ( !shdr ) { + goto Fail; + } + 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 * ) (size_t) 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( const 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 <= (unsigned int) ( _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( const char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ){ + unsigned int id, 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 0000000..fc2f642 --- /dev/null +++ b/libs/picomodel/lwo/pntspols.c @@ -0,0 +1,588 @@ +/* + ====================================================================== + 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 = ( size_t ) 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 * ) (size_t) 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 0000000..1b2c1fc --- /dev/null +++ b/libs/picomodel/lwo/surface.c @@ -0,0 +1,1108 @@ +/* + ====================================================================== + 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 (*)(void *)) lwFreePlugin); + + void (*freeTexture)(void *) = (void (*)(void *)) lwFreeTexture; + lwListFree(surf->color.tex, freeTexture); + lwListFree(surf->luminosity.tex, freeTexture); + lwListFree(surf->diffuse.tex, freeTexture); + lwListFree(surf->specularity.tex, freeTexture); + lwListFree(surf->glossiness.tex, freeTexture); + lwListFree(surf->reflection.val.tex, freeTexture); + lwListFree(surf->transparency.val.tex, freeTexture); + lwListFree(surf->eta.tex, freeTexture); + lwListFree(surf->translucency.tex, freeTexture); + lwListFree(surf->bump.tex, freeTexture); + + _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, (int (*)(void *, 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, (int (*)(void *, 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 0000000..f4ada60 --- /dev/null +++ b/libs/picomodel/lwo/vecmath.c @@ -0,0 +1,34 @@ +/* + ====================================================================== + 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 0000000..8976572 --- /dev/null +++ b/libs/picomodel/lwo/vmap.c @@ -0,0 +1,266 @@ +/* + ====================================================================== + 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 0000000..018ee3a --- /dev/null +++ b/libs/picomodel/picointernal.c @@ -0,0 +1,1349 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* todo: + * - fix p->curLine for parser routines. increased twice + */ + +/* dependencies */ +#include +#include "picointernal.h" +#include "globaldefs.h" + + + +/* function pointers */ +void *( *_pico_ptr_malloc )( size_t ) = malloc; +void ( *_pico_ptr_free )( void* ) = free; +void ( *_pico_ptr_load_file )( const 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( const 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 ]; +} + +#if GDEF_ARCH_ENDIAN_BIG + +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 + */ +const char *_pico_stristr( const 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; pos < bufsize && pos < destsize; pos++ ) + { + if ( buf[pos] == '\n' ) { + pos++; break; + } + dest[pos] = buf[pos]; + } + /* terminate dest and return */ + dest[pos] = '\0'; + return pos; +} + +/* _pico_parse_skip_white: + * skips white spaces in current pico parser, sets *hasLFs + * to 1 if linefeeds were skipped, and either returns the + * parser's cursor pointer or NULL on error. -sea + */ +void _pico_parse_skip_white( picoParser_t *p, int *hasLFs ){ + /* sanity checks */ + if ( p == NULL || p->cursor == 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( const 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 = (const char *) buffer; + p->cursor = p->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; + const 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( const 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 0000000..4bcbd4c --- /dev/null +++ b/libs/picomodel/picointernal.h @@ -0,0 +1,208 @@ +/* ----------------------------------------------------------------------------- + + 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 + +#include "globaldefs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/* dependencies */ +#include +#include +#include +#include +#include +#include + +#include "picomodel.h" + + +/* os dependent replacements */ +#if GDEF_OS_WINDOWS + #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 +{ + const char *buffer; + int bufSize; + char *token; + int tokenSize; + int tokenMax; + const char *cursor; + const char *max; + int curLine; +} +picoParser_t; + +typedef struct picoMemStream_s +{ + const picoByte_t *buffer; + int bufSize; + const 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 )( const 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( const 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, ... ); +const char *_pico_stristr( const 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( const 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( const 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 0000000..27ce8b1 --- /dev/null +++ b/libs/picomodel/picomodel.c @@ -0,0 +1,2317 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* 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 )( const 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, const 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( const 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, const char *fileName ){ + 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'; + + 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, const 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, const 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, const 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, const 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 & */ +const int 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, const char *name, 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 ( !name || !strcmp( workSurface->name, name ) ) { + 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, name ? name : 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/picomodules.c b/libs/picomodel/picomodules.c new file mode 100644 index 0000000..6eff237 --- /dev/null +++ b/libs/picomodel/picomodules.c @@ -0,0 +1,89 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* 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; +extern const picoModule_t picoModuleIQM; + + + +/* 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 */ + &picoModuleIQM, /* inter-quake model */ + 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 0000000..e72454a --- /dev/null +++ b/libs/picomodel/pm_3ds.c @@ -0,0 +1,774 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* 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; i < max; i++ ) + { + if ( debugChunkNames[i].id == id ) { + /* gaynux update -sea */ + return _pico_strlwr( debugChunkNames[i].name ); + } + } + return "chunk_unknown"; +} +#endif /*DEBUG_PM_3DS*/ + +/* this funky loader needs byte alignment */ +#pragma pack(push, 1) + +typedef struct S3dsIndices +{ + unsigned short a,b,c; + unsigned short visible; +} +T3dsIndices; + +typedef struct S3dsChunk +{ + unsigned short id; + unsigned int len; +} +T3dsChunk; + +/* restore previous data alignment */ +#pragma pack(pop) + +/* _3ds_canload: + * validates an autodesk 3ds model file. + */ +static int _3ds_canload( PM_PARAMS_CANLOAD ){ + const T3dsChunk *chunk; + + /* sanity check */ + if ( bufSize < (int) sizeof( T3dsChunk ) ) { + return PICO_PMV_ERROR_SIZE; + } + + /* get pointer to 3ds header chunk */ + chunk = (const T3dsChunk *)buffer; + + /* check data length */ + if ( bufSize < (int) _pico_little_long( chunk->len ) ) { + 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; i < numVerts; i++ ) + { + picoVec3_t v; + v[0] = GetFloat( pers ); + v[1] = GetFloat( pers ); /* ydnar: unflipped */ + v[2] = GetFloat( pers ); /* ydnar: unflipped and negated */ + + /* add current vertex */ + PicoSetSurfaceXYZ( pers->surface,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; i < numFaces; i++ ) + { + /* remember, we only need 3 of 4 values read in for each */ + /* face. the 4th value is a vis flag for 3dsmax which is */ + /* being ignored by us here */ + T3dsIndices face; + face.a = GetWord( pers ); + face.c = GetWord( pers ); /* ydnar: flipped order */ + face.b = GetWord( pers ); /* ydnar: flipped order */ + face.visible = GetWord( pers ); + + /* copy indexes */ + PicoSetSurfaceIndex( pers->surface, ( 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; i < numTexCoords; i++ ) + { + picoVec2_t uv; + uv[0] = GetFloat( pers ); + uv[1] = -GetFloat( pers ); /* ydnar: we use origin at bottom */ + + /* to make sure we don't mess up memory */ + if ( pers->surface == 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; i < numSharedVerts; i++ ) + { + GetWord( pers ); + } + /* success (no errors occured) */ + return 1; +} + +static int GetDiffuseColor( T3dsLoaderPers *pers ){ + /* todo: support all 3ds specific color formats; */ + /* that means: rgb,tru,trug,rgbg */ + + /* get rgb color (range 0..255; 3 bytes) */ + picoColor_t color; + + color[0] = GetByte( pers ); + color[1] = GetByte( pers ); + color[2] = GetByte( pers ); + color[3] = 255; + + /* store this as the current shader's diffuse color */ + if ( pers->shader ) { + 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 *)_pico_alloc( bufSize ); + memcpy( pers.bufptr, buffer, bufSize ); + 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 0000000..d73187c --- /dev/null +++ b/libs/picomodel/pm_ase.c @@ -0,0 +1,1181 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* 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; + } + + /* create pico parser */ + p = _pico_new_parser( (const 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, const char *name ){ + 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, name, 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( (const 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, lastNodeName ); + _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 = NULL; + 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, lastNodeName ); + _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 0000000..c408618 --- /dev/null +++ b/libs/picomodel/pm_fm.c @@ -0,0 +1,662 @@ +/* ----------------------------------------------------------------------------- + + 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. + */ + +/* 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, *bb0; + int fm_file_pos; + + bb0 = bb = (picoByte_t*) _pico_alloc( bufSize ); + memcpy( bb, buffer, bufSize ); + + // 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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 + _pico_free( bb0 ); + 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; + 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, *bb0; + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + bb0 = bb = (picoByte_t*) _pico_alloc( bufSize ); + memcpy( bb, buffer, bufSize ); + + // 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" ); + _pico_free( bb0 ); + return NULL; + } + + if ( _pico_little_long( fm.fm_header_hdr->version ) != FM_HEADERCHUNKVER ) { + _pico_printf( PICO_WARNING, "FM Header Version incorrect\n" ); + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + return NULL; + } + + if ( _pico_little_long( fm.fm_skin_hdr->version ) != FM_SKINCHUNKVER ) { + _pico_printf( PICO_WARNING, "FM Skin Version incorrect\n" ); + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + return NULL; + } + + if ( _pico_little_long( fm.fm_st_hdr->version ) != FM_STCOORDCHUNKVER ) { + _pico_printf( PICO_WARNING, "FM ST Version incorrect\n" ); + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + return NULL; + } + + if ( _pico_little_long( fm.fm_tri_hdr->version ) != FM_TRISCHUNKVER ) { + _pico_printf( PICO_WARNING, "FM Tri Version incorrect\n" ); + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + return NULL; + } + + if ( _pico_little_long( fm.fm_frame_hdr->version ) != FM_FRAMESCHUNKVER ) { + _pico_printf( PICO_WARNING, "FM Frame Version incorrect\n" ); + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + return NULL; + } + + if ( frameNum < 0 || frameNum >= fm_head->numFrames ) { + _pico_printf( PICO_ERROR, "Invalid or out-of-range FM frame specified" ); + _pico_free( bb0 ); + 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, (const 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" ); + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + 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; i < fm_head->numXYZ; 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. + dups = 0; + triangle = tri_verts; + + for ( i = 0; i < fm_head->numTris; 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; i < fm_head->numXYZ; 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; i < fm_head->numXYZ; 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; i < dup_index; i++ ) + _pico_printf( PICO_NORMAL, " Dup Index #%d OldVert: %d ST: %d\n", i, p_index_LUT_DUPS[i].OldVert, p_index_LUT_DUPS[i].ST ); + + triangle = tri_verts; + for ( i = 0; i < fm_head->numTris; 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; i < dups; i++ ) + { + j = p_index_LUT_DUPS[i].OldVert; + /* set vertex origin */ + xyz[ 0 ] = frame->verts[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; i < fm_head->numXYZ; 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 */ + _pico_free( bb0 ); + 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 0000000..6fa317c --- /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_iqm.c b/libs/picomodel/pm_iqm.c new file mode 100644 index 0000000..c480872 --- /dev/null +++ b/libs/picomodel/pm_iqm.c @@ -0,0 +1,370 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* dependencies */ +#include "picointernal.h" +#include "bytebool.h" + +extern const picoModule_t picoModuleIQM; + +#define IQM_MAGIC "INTERQUAKEMODEL" //15+null + +/* + ======================================================================== + + .IQM triangle model file format + + ======================================================================== + */ + +enum +{ + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, + IQM_CUSTOM = 0x10 +}; + +enum +{ + IQM_BYTE = 0, + IQM_UBYTE = 1, + IQM_SHORT = 2, + IQM_USHORT = 3, + IQM_INT = 4, + IQM_UINT = 5, + IQM_HALF = 6, + IQM_FLOAT = 7, + IQM_DOUBLE = 8 +}; + +// animflags +#define IQM_LOOP 1 + +typedef struct iqmHeader_s { + byte id[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_neighbors; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; +} iqmHeader_t; + +typedef struct iqmmesh_s { + unsigned int name; + unsigned int material; + unsigned int first_vertex; + unsigned int num_vertexes; + unsigned int first_triangle; + unsigned int num_triangles; +} iqmmesh_t; + +typedef struct iqmvertexarray_s { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; +} iqmvertexarray_t; + + +//is anyone actually going to run this on a big-endian cpu? +static iqmHeader_t SwapHeader(const iqmHeader_t *h) +{ + iqmHeader_t r = *h; + r.version = _pico_little_long(h->version); + r.filesize = _pico_little_long(h->filesize); + r.flags = _pico_little_long(h->flags); + r.num_text = _pico_little_long(h->num_text); + r.ofs_text = _pico_little_long(h->ofs_text); + r.num_meshes = _pico_little_long(h->num_meshes); + r.ofs_meshes = _pico_little_long(h->ofs_meshes); + r.num_vertexarrays = _pico_little_long(h->num_vertexarrays); + r.num_vertexes = _pico_little_long(h->num_vertexes); + r.ofs_vertexarrays = _pico_little_long(h->ofs_vertexarrays); + r.num_triangles = _pico_little_long(h->num_triangles); + r.ofs_triangles = _pico_little_long(h->ofs_triangles); + r.ofs_neighbors = _pico_little_long(h->ofs_neighbors); + r.num_joints = _pico_little_long(h->num_joints); + r.ofs_joints = _pico_little_long(h->ofs_joints); + r.num_poses = _pico_little_long(h->num_poses); + r.ofs_poses = _pico_little_long(h->ofs_poses); + r.num_anims = _pico_little_long(h->num_anims); + r.ofs_anims = _pico_little_long(h->ofs_anims); + r.num_frames = _pico_little_long(h->num_frames); + r.num_framechannels = _pico_little_long(h->num_framechannels); + r.ofs_frames = _pico_little_long(h->ofs_frames); + r.ofs_bounds = _pico_little_long(h->ofs_bounds); + r.num_comment = _pico_little_long(h->num_comment); + r.ofs_comment = _pico_little_long(h->ofs_comment); + r.num_extensions = _pico_little_long(h->num_extensions); + r.ofs_extensions = _pico_little_long(h->ofs_extensions); + return r; +} + + +// _iqm_canload() + +static int _iqm_canload( PM_PARAMS_CANLOAD ){ + iqmHeader_t h; + + //make sure there's enough data for the header... + if ((size_t)bufSize < sizeof(h)) + return PICO_PMV_ERROR_SIZE; + h = SwapHeader(buffer); + + //make sure its actually an iqm + if (memcmp(h.id, IQM_MAGIC, sizeof(h.id))) + return PICO_PMV_ERROR_IDENT; + //v1 is flawed, we don't know about anything higher either. + if (h.version != 2) + return PICO_PMV_ERROR_VERSION; + //make sure its not truncated + if ((size_t)h.filesize != (size_t)bufSize) + return PICO_PMV_ERROR_SIZE; + + //looks like we can probably use it. + return PICO_PMV_OK; +} + + + +// _iqm_load() loads an interquake model file. + + +static picoModel_t *_iqm_load( PM_PARAMS_LOAD ){ + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + const float *inf; + const byte *inb; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + iqmHeader_t h; + iqmmesh_t m; + iqmvertexarray_t a; + size_t s, t, j, i; + const char *stringtable; + char skinname[512]; + const unsigned int *tri; + + //just in case + if (_iqm_canload(fileName, buffer, bufSize) != PICO_PMV_OK) + { + _pico_printf( PICO_ERROR, "%s is not an IQM File!", fileName ); + return NULL; + } + h = SwapHeader(buffer); + stringtable = (const char*)buffer + h.ofs_text; + + // do frame check + if ( h.num_anims != 0 ) { + _pico_printf( PICO_WARNING, "%s has animations! Using base pose only.", fileName ); + } + + /* 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, 1 ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + for (s = 0; s < h.num_meshes; s++) + { + m = ((const iqmmesh_t*)((const char*)buffer + h.ofs_meshes))[s]; + m.first_triangle = _pico_little_long(m.first_triangle); + m.first_vertex = _pico_little_long(m.first_vertex); + m.material = _pico_little_long(m.material); + m.name = _pico_little_long(m.name); + m.num_triangles = _pico_little_long(m.num_triangles); + m.num_vertexes = _pico_little_long(m.num_vertexes); + + // 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; + } + + // detox Skin name + memcpy(skinname, stringtable+m.material, sizeof(skinname)); + _pico_setfext( skinname, "" ); + _pico_unixify( skinname ); + + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( picoSurface, stringtable+m.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 ); + + + // spew the surface's indexes + tri = (const unsigned int *)((const char *)buffer+h.ofs_triangles) + m.first_triangle*3; + for (t = 0; t < m.num_triangles*3; t++) + PicoSetSurfaceIndex( picoSurface, t, _pico_little_long(*tri++) - m.first_vertex ); + + for ( j = 0; j < h.num_vertexarrays; j++) + { + a = ((const iqmvertexarray_t*)((const char*)buffer + h.ofs_vertexarrays))[j]; + a.flags = _pico_little_long(a.flags); + a.format = _pico_little_long(a.format); + a.offset = _pico_little_long(a.offset); + a.size = _pico_little_long(a.size); + a.type = _pico_little_long(a.type); + + switch(a.type) + { + case IQM_POSITION: + if (a.format == IQM_FLOAT && a.size >= 3) + { + inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size; + for ( i = 0; i < m.num_vertexes; i++, inf += a.size ) + { + xyz[0] = _pico_little_float(inf[0]); + xyz[1] = _pico_little_float(inf[1]); + xyz[2] = _pico_little_float(inf[2]); + PicoSetSurfaceXYZ( picoSurface, i, xyz ); + } + } + break; + case IQM_TEXCOORD: + if (a.format == IQM_FLOAT && a.size >= 2) + { + inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size; + for ( i = 0; i < m.num_vertexes; i++, inf += a.size ) + { + st[0] = _pico_little_float(inf[0]); + st[1] = _pico_little_float(inf[1]); + PicoSetSurfaceST( picoSurface, 0, i, st ); + } + } + break; + case IQM_NORMAL: + if (a.format == IQM_FLOAT && a.size >= 3) + { + inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size; + for ( i = 0; i < m.num_vertexes; i++, inf += a.size ) + { + normal[0] = _pico_little_float(inf[0]); + normal[1] = _pico_little_float(inf[1]); + normal[2] = _pico_little_float(inf[2]); + PicoSetSurfaceNormal( picoSurface, i, normal ); + } + } + break; + case IQM_COLOR: + if (a.format == IQM_UBYTE && a.size >= 3) + { + inb = (const byte*)((const char *)buffer + a.offset) + m.first_vertex*a.size; + for ( i = 0; i < m.num_vertexes; i++, inb += a.size ) + { + color[0] = inb[0]; + color[1] = inb[1]; + color[2] = inb[2]; + color[3] = (a.size>=4)?inb[3]:255; + PicoSetSurfaceColor( picoSurface, 0, i, color ); + } + } + else if (a.format == IQM_FLOAT && a.size >= 3) + { + inf = (const float*)((const char *)buffer + a.offset) + m.first_vertex*a.size; + for ( i = 0; i < m.num_vertexes; i++, inf += a.size ) + { + color[0] = inf[0]*255; + color[1] = inf[1]*255; + color[2] = inf[2]*255; + color[3] = (a.size>=4)?inf[3]*255:255; + PicoSetSurfaceColor( picoSurface, 0, i, color ); + } + } + break; + case IQM_TANGENT: + case IQM_BLENDINDEXES: + case IQM_BLENDWEIGHTS: + case IQM_CUSTOM: + break; //these attributes are not relevant. + } + } + } + + // oh look, we're done. + return picoModel; +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleIQM = +{ + "0.001", /* module version string */ + "Vera Visions Model", /* module display name */ + "Spoike", /* author's name */ + "2018 Spoike", /* module copyright */ + { + "vvm", NULL, NULL, NULL /* default extensions to use */ + }, + _iqm_canload, /* validation routine */ + _iqm_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_lwo.c b/libs/picomodel/pm_lwo.c new file mode 100644 index 0000000..6cbf0f4 --- /dev/null +++ b/libs/picomodel/pm_lwo.c @@ -0,0 +1,420 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* 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( (const 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( (const 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 0000000..37b5466 --- /dev/null +++ b/libs/picomodel/pm_md2.c @@ -0,0 +1,659 @@ +/* ----------------------------------------------------------------------------- + + 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. + */ + +/* dependencies */ +#include "picointernal.h" +#include "bytebool.h" + +/* md2 model format */ +const char *MD2_MAGIC = "IDP2"; +const int MD2_VERSION = 8; + +#define MD2_NUMVERTEXNORMALS 162 + +const int MD2_MAX_TRIANGLES = 4096; +const int MD2_MAX_VERTS = 2048; +const int MD2_MAX_FRAMES = 512; +const int MD2_MAX_MD2SKINS = 32; +const int MD2_MAX_SKINNAME = 64; + +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 ){ + const md2_t *md2; + + /* sanity check */ + if ( (size_t) bufSize < ( sizeof( *md2 ) * 2 ) ) { + return PICO_PMV_ERROR_SIZE; + } + + /* set as md2 */ + md2 = (const md2_t*) buffer; + + /* check md2 magic */ + if ( *( (const int*) md2->magic ) != *( (const 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; + 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, *bb0; + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* set as md2 */ + bb0 = bb = (picoByte_t*) _pico_alloc( bufSize ); + memcpy( bb, buffer, bufSize ); + md2 = (md2_t*) bb; + + /* check ident and version */ + if ( *( (const int*) md2->magic ) != *( (const 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 ); + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + return NULL; + } + + if ( frameNum < 0 || frameNum >= md2->numFrames ) { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MD2 frame specified" ); + _pico_free( bb0 ); + 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, (const char *) ( 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" ); + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + 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; i < md2->numXYZ; 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. + dups = 0; + for ( i = 0; i < md2->numTris; 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; i < md2->numXYZ; 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; i < dups; i++ ) + { + j = p_index_LUT_DUPS[i].OldVert; + /* set vertex origin */ + xyz[ 0 ] = frame->verts[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; i < md2->numXYZ; 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 */ + _pico_free( bb0 ); + 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 0000000..d31806b --- /dev/null +++ b/libs/picomodel/pm_md3.c @@ -0,0 +1,418 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* dependencies */ +#include "picointernal.h" + + + +/* md3 model format */ +const char *MD3_MAGIC = "IDP3"; +const int MD3_VERSION = 15; + +/* md3 vertex scale */ +const float 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 ){ + const md3_t *md3; + + + /* sanity check */ + if ( (size_t) bufSize < ( sizeof( *md3 ) * 2 ) ) { + return PICO_PMV_ERROR_SIZE; + } + + /* set as md3 */ + md3 = (const md3_t*) buffer; + + /* check md3 magic */ + if ( *( (const int*) md3->magic ) != *( (const 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, *bb0; + 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 */ + bb0 = bb = (picoByte_t*) _pico_alloc( bufSize ); + memcpy( bb, buffer, bufSize ); + md3 = (md3_t*) bb; + + /* 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) */ + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + return NULL; + } + + if ( frameNum < 0 || frameNum >= md3->numFrames ) { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MD3 frame specified" ); + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + 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 */ + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + 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 */ + _pico_free( bb0 ); + 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 0000000..235dcec --- /dev/null +++ b/libs/picomodel/pm_mdc.c @@ -0,0 +1,742 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* dependencies */ +#include "picointernal.h" + +/* mdc model format */ +const char *MDC_MAGIC = "IDPC"; +const int MDC_VERSION = 2; + +/* mdc vertex scale */ +const float MDC_SCALE = ( 1.0f / 64.0f ); +const float MDC_MAX_OFS = 127.0f; +const float 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 ){ + const mdc_t *mdc; + + + /* sanity check */ + if ( (size_t) bufSize < ( sizeof( *mdc ) * 2 ) ) { + return PICO_PMV_ERROR_SIZE; + } + + /* set as mdc */ + mdc = (const mdc_t*) buffer; + + /* check mdc magic */ + if ( *( (const int*) mdc->magic ) != *( (const 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, *bb0; + mdc_t *mdc; + mdcSurface_t *surface; + mdcShader_t *shader; + mdcTexCoord_t *texCoord; + mdcFrame_t *frame; + mdcTriangle_t *triangle; + mdcVertex_t *vertex; + mdcXyzCompressed_t *vertexComp = NULL; + short *mdcShort, *mdcCompVert = NULL; + 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 */ + bb0 = bb = (picoByte_t*) _pico_alloc( bufSize ); + memcpy( bb, buffer, bufSize ); + mdc = (mdc_t*) bb; + + /* 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) */ + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + return NULL; + } + + if ( frameNum < 0 || frameNum >= mdc->numFrames ) { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MDC frame specified" ); + _pico_free( bb0 ); + 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" ); + _pico_free( bb0 ); + 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 */ + _pico_free( bb0 ); + 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 ); + _pico_free( bb0 ); + 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 */ + _pico_free( bb0 ); + 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 0000000..378265f --- /dev/null +++ b/libs/picomodel/pm_ms3d.c @@ -0,0 +1,496 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* dependencies */ +#include "picointernal.h" +#include "globaldefs.h" + +/* disable warnings */ +#if GDEF_COMPILER_MSVC +#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 */ +const int MS3D_MAX_VERTS = 8192; +const int MS3D_MAX_TRIS = 16384; +const int MS3D_MAX_GROUPS = 128; +const int MS3D_MAX_MATERIALS = 128; +const int MS3D_MAX_JOINTS = 128; +const int MS3D_MAX_KEYFRAMES = 216; + +/* ms3d flags */ +const int MS3D_SELECTED = 1; +const int MS3D_HIDDEN = 2; +const int MS3D_SELECTED2 = 4; +const int 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 ){ + const TMsHeader *hdr; + + + /* sanity check */ + if ( (size_t) bufSize < sizeof( TMsHeader ) ) { + return PICO_PMV_ERROR_SIZE; + } + + /* get ms3d header */ + hdr = (const 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, *bufptr0; + 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 ); + + bufptr0 = bufptr = (picoByte_t*) _pico_alloc( bufSize ); + memcpy( bufptr, buffer, bufSize ); + /* skip header */ + bufptr += 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; i < numVerts; i++ ) + { + TMsVertex *vertex; + vertex = (TMsVertex *)bufptr; + bufptr += sizeof( TMsVertex ); + + vertex->xyz[ 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; i < numTris; i++ ) + { + TMsTriangle *triangle; + triangle = (TMsTriangle *)bufptr; + bufptr += sizeof( TMsTriangle ); + + triangle->flags = _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 ); + _pico_free( bufptr0 ); + return NULL; /* yuck */ + } + } + } + /* get number of groups */ + bufptr = GetWord( bufptr,&numGroups ); +// ptrToGroups = bufptr; + +#ifdef DEBUG_PM_MS3D + printf( "NumGroups: %d\n",numGroups ); +#endif + /* run through all groups in model */ + for ( i = 0; i < numGroups && i < MS3D_MAX_GROUPS; i++ ) + { + picoSurface_t *surface; + TMsGroup *group; + + group = (TMsGroup *)bufptr; + bufptr += sizeof( TMsGroup ); + + /* we ignore hidden groups */ + if ( group->flags & MS3D_HIDDEN ) { + bufptr += ( group->numTriangles * 2 ) + 1; + continue; + } + /* forced null term of group name */ + group->name[ 31 ] = '\0'; + + /* create new pico surface */ + surface = PicoNewSurface( model ); + if ( surface == NULL ) { + PicoFreeModel( model ); + _pico_free( bufptr0 ); + return NULL; + } + /* do surface setup */ + PicoSetSurfaceType( surface,PICO_TRIANGLES ); + PicoSetSurfaceName( surface,group->name ); + + /* process triangle indices */ + for ( k = 0; k < group->numTriangles; k++ ) + { + TMsTriangle *triangle; + unsigned int triangleIndex; + + /* get triangle index */ + bufptr = GetWord( bufptr,(int *)&triangleIndex ); + + /* get ptr to triangle data */ + triangle = (TMsTriangle *)( ptrToTris + ( sizeof( TMsTriangle ) * triangleIndex ) ); + + /* run through triangle vertices */ + for ( m = 0; m < 3; m++ ) + { + TMsVertex *vertex; + unsigned int vertexIndex; + picoVec2_t texCoord; + + /* get ptr to vertex data */ + vertexIndex = triangle->vertexIndices[ m ]; + vertex = (TMsVertex *)( ptrToVerts + ( sizeof( TMsVertex ) * vertexIndex ) ); + + /* store vertex origin */ + PicoSetSurfaceXYZ( surface,vertexIndex,vertex->xyz ); + + /* store vertex color */ + PicoSetSurfaceColor( surface,0,vertexIndex,white ); + + /* store vertex normal */ + PicoSetSurfaceNormal( surface,vertexIndex,triangle->vertexNormals[ m ] ); + + /* store current face vertex index */ + PicoSetSurfaceIndex( surface,( k * 3 + ( 2 - m ) ),(picoIndex_t)vertexIndex ); + + /* get texture vertex coord */ + texCoord[ 0 ] = triangle->s[ m ]; + texCoord[ 1 ] = -triangle->t[ m ]; /* flip t */ + + /* store texture vertex coord */ + PicoSetSurfaceST( surface,0,vertexIndex,texCoord ); + } + } + /* store material */ + shaderRefs[ i ] = *bufptr++; + +#ifdef DEBUG_PM_MS3D + printf( "Group %d: '%s' (%d tris)\n",i,group->name,group->numTriangles ); +#endif + } + /* get number of materials */ + bufptr = GetWord( bufptr,&numMaterials ); + +#ifdef DEBUG_PM_MS3D + printf( "NumMaterials: %d\n",numMaterials ); +#endif + /* run through all materials in model */ + for ( i = 0; i < numMaterials; i++ ) + { + picoShader_t *shader; + picoColor_t ambient,diffuse,specular; + TMsMaterial *material; + int k; + + material = (TMsMaterial *)bufptr; + bufptr += sizeof( TMsMaterial ); + + /* null term strings */ + material->name [ 31 ] = '\0'; + material->texture [ 127 ] = '\0'; + material->alphamap[ 127 ] = '\0'; + + /* ltrim strings */ + _pico_strltrim( material->name ); + _pico_strltrim( material->texture ); + _pico_strltrim( material->alphamap ); + + /* rtrim strings */ + _pico_strrtrim( material->name ); + _pico_strrtrim( material->texture ); + _pico_strrtrim( material->alphamap ); + + /* create new pico shader */ + shader = PicoNewShader( model ); + if ( shader == NULL ) { + PicoFreeModel( model ); + _pico_free( bufptr0 ); + return NULL; + } + /* scale shader colors */ + for ( k = 0; k < 4; k++ ) + { + ambient [ k ] = (picoByte_t) ( material->ambient[ k ] * 255 ); + diffuse [ k ] = (picoByte_t) ( material->diffuse[ k ] * 255 ); + specular[ k ] = (picoByte_t) ( material->specular[ k ] * 255 ); + } + /* set shader colors */ + PicoSetShaderAmbientColor( shader,ambient ); + PicoSetShaderDiffuseColor( shader,diffuse ); + PicoSetShaderSpecularColor( shader,specular ); + + /* set shader transparency */ + PicoSetShaderTransparency( shader,material->transparency ); + + /* set shader shininess (0..127) */ + PicoSetShaderShininess( shader,material->shininess ); + + /* set shader name */ + PicoSetShaderName( shader,material->name ); + + /* set shader texture map name */ + PicoSetShaderMapName( shader,material->texture ); + +#ifdef DEBUG_PM_MS3D + printf( "Material %d: '%s' ('%s','%s')\n",i,material->name,material->texture,material->alphamap ); +#endif + } + /* assign shaders to surfaces */ + for ( i = 0; i < numGroups && i < MS3D_MAX_GROUPS; i++ ) + { + picoSurface_t *surface; + picoShader_t *shader; + + /* sanity check */ + if ( shaderRefs[ i ] >= MS3D_MAX_MATERIALS || + shaderRefs[ i ] < 0 ) { + continue; + } + + /* get surface */ + surface = PicoGetModelSurface( model,i ); + if ( surface == NULL ) { + continue; + } + + /* get shader */ + shader = PicoGetModelShader( model,shaderRefs[ i ] ); + if ( shader == NULL ) { + continue; + } + + /* assign shader */ + PicoSetSurfaceShader( surface,shader ); + +#ifdef DEBUG_PM_MS3D + printf( "Mapped: %d ('%s') to %d (%s)\n", + shaderRefs[i],shader->name,i,surface->name ); +#endif + } + /* return allocated pico model */ + _pico_free( bufptr0 ); + return model; +// return NULL; +} + +/* pico file format module definition */ +const picoModule_t picoModuleMS3D = +{ + "0.4-a", /* module version string */ + "Milkshape 3D", /* module display name */ + "seaw0lf", /* author's name */ + "2002 seaw0lf", /* module copyright */ + { + "ms3d",NULL,NULL,NULL /* default extensions to use */ + }, + _ms3d_canload, /* validation routine */ + _ms3d_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_obj.c b/libs/picomodel/pm_obj.c new file mode 100644 index 0000000..e342ab0 --- /dev/null +++ b/libs/picomodel/pm_obj.c @@ -0,0 +1,946 @@ +/* ----------------------------------------------------------------------------- + + 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. + + ----------------------------------------------------------------------------- */ + +/* dependencies */ +#include "picointernal.h" +#include "globaldefs.h" + +/* disable warnings */ +#if GDEF_COMPILER_MSVC +#pragma warning( disable:4100 ) /* unref param */ +#endif + +/* todo: + * - '_obj_load' code crashes in a weird way after + * '_obj_mtl_load' for a few .mtl files + * - process 'mtllib' rather than using .mtl + * - handle 'usemtl' statements + */ +/* uncomment when debugging this module */ +/* #define DEBUG_PM_OBJ */ +/* #define DEBUG_PM_OBJ_EX */ + +/* this holds temporary vertex data read by parser */ +typedef struct SObjVertexData +{ + picoVec3_t v; /* geometric vertices */ + picoVec2_t vt; /* texture vertices */ + picoVec3_t vn; /* vertex normals (optional) */ +} +TObjVertexData; + +/* _obj_canload: + * validates a wavefront obj model file. + */ +static int _obj_canload( PM_PARAMS_CANLOAD ){ + picoParser_t *p; + + /* check data length */ + if ( bufSize < 30 ) { + return PICO_PMV_ERROR_SIZE; + } + + /* first check file extension. we have to do this for objs */ + /* cause there is no good way to identify the contents */ + if ( _pico_stristr( fileName,".obj" ) != NULL || + _pico_stristr( fileName,".wf" ) != NULL ) { + return PICO_PMV_OK; + } + /* if the extension check failed we parse through the first */ + /* few lines in file and look for common keywords often */ + /* appearing at the beginning of wavefront objects */ + + /* alllocate a new pico parser */ + p = _pico_new_parser( (const picoByte_t *)buffer,bufSize ); + if ( p == NULL ) { + return PICO_PMV_ERROR_MEMORY; + } + + /* parse obj head line by line for type check */ + while ( 1 ) + { + /* get first token on line */ + if ( _pico_parse_first( p ) == NULL ) { + break; + } + + /* we only parse the first few lines, say 80 */ + if ( p->curLine > 80 ) { + break; + } + + /* skip empty lines */ + if ( p->token == NULL || !strlen( p->token ) ) { + continue; + } + + /* material library keywords are teh good */ + if ( !_pico_stricmp( p->token,"usemtl" ) || + !_pico_stricmp( p->token,"mtllib" ) || + !_pico_stricmp( p->token,"g" ) || + !_pico_stricmp( p->token,"v" ) ) { /* v,g bit fishy, but uh... */ + /* free the pico parser thing */ + _pico_free_parser( p ); + + /* seems to be a valid wavefront obj */ + return PICO_PMV_OK; + } + /* skip rest of line */ + _pico_parse_skip_rest( p ); + } + /* free the pico parser thing */ + _pico_free_parser( p ); + + /* doesn't really look like an obj to us */ + return PICO_PMV_ERROR; +} + +/* SizeObjVertexData: + * This pretty piece of 'alloc ahead' code dynamically + * allocates - and reallocates as soon as required - + * my vertex data array in even steps. + */ +const int SIZE_OBJ_STEP = 4096; + +static TObjVertexData *SizeObjVertexData( + TObjVertexData *vertexData, int reqEntries, + int *entries, int *allocated ){ + int newAllocated; + + /* sanity checks */ + if ( reqEntries < 1 ) { + return NULL; + } + if ( entries == NULL || allocated == NULL ) { + return NULL; /* must have */ + + } + /* no need to grow yet */ + if ( vertexData && ( reqEntries < *allocated ) ) { + *entries = reqEntries; + return vertexData; + } + /* given vertex data ptr not allocated yet */ + if ( vertexData == NULL ) { + /* how many entries to allocate */ + newAllocated = ( reqEntries > SIZE_OBJ_STEP ) ? + reqEntries : SIZE_OBJ_STEP; + + /* throw out an extended debug message */ +#ifdef DEBUG_PM_OBJ_EX + printf( "SizeObjVertexData: allocate (%d entries)\n", + newAllocated ); +#endif + /* first time allocation */ + vertexData = (TObjVertexData *) + _pico_alloc( sizeof( TObjVertexData ) * newAllocated ); + + /* allocation failed */ + if ( vertexData == NULL ) { + return NULL; + } + + /* allocation succeeded */ + *allocated = newAllocated; + *entries = reqEntries; + return vertexData; + } + /* given vertex data ptr needs to be resized */ + if ( reqEntries == *allocated ) { + newAllocated = ( *allocated + SIZE_OBJ_STEP ); + + /* throw out an extended debug message */ +#ifdef DEBUG_PM_OBJ_EX + printf( "SizeObjVertexData: reallocate (%d entries)\n", + newAllocated ); +#endif + /* try to reallocate */ + vertexData = (TObjVertexData *) + _pico_realloc( (void *)&vertexData, + sizeof( TObjVertexData ) * ( *allocated ), + sizeof( TObjVertexData ) * ( newAllocated ) ); + + /* reallocation failed */ + if ( vertexData == NULL ) { + return NULL; + } + + /* reallocation succeeded */ + *allocated = newAllocated; + *entries = reqEntries; + return vertexData; + } + /* we're b0rked when we reach this */ + return NULL; +} + +static void FreeObjVertexData( TObjVertexData *vertexData ){ + if ( vertexData != NULL ) { + free( (TObjVertexData *)vertexData ); + } +} + +static int _obj_mtl_load( picoModel_t *model ){ + picoShader_t *curShader = NULL; + picoParser_t *p; + picoByte_t *mtlBuffer; + int mtlBufSize; + char *fileName; + + /* sanity checks */ + if ( model == NULL || model->fileName == NULL ) { + return 0; + } + + /* skip if we have a zero length model file name */ + if ( !strlen( model->fileName ) ) { + return 0; + } + + /* helper */ + #define _obj_mtl_error_return \ + { \ + _pico_free_parser( p ); \ + _pico_free_file( mtlBuffer ); \ + _pico_free( fileName ); \ + return 0; \ + } + /* alloc copy of model file name */ + fileName = _pico_clone_alloc( model->fileName ); + if ( fileName == NULL ) { + return 0; + } + + /* change extension of model file to .mtl */ + _pico_setfext( fileName, "mtl" ); + + /* load .mtl file contents */ + _pico_load_file( fileName,&mtlBuffer,&mtlBufSize ); + + /* check result */ + if ( mtlBufSize == 0 ) { + return 1; /* file is empty: no error */ + } + if ( mtlBufSize < 0 ) { + return 0; /* load failed: error */ + + } + /* create a new pico parser */ + p = _pico_new_parser( mtlBuffer, mtlBufSize ); + if ( p == NULL ) { + _obj_mtl_error_return; + } + + /* doo teh .mtl parse */ + while ( 1 ) + { + /* get next token in material file */ + if ( _pico_parse( p,1 ) == NULL ) { + break; + } +#if 1 + + /* skip empty lines */ + if ( p->token == NULL || !strlen( p->token ) ) { + continue; + } + + /* skip comment lines */ + if ( p->token[0] == '#' ) { + _pico_parse_skip_rest( p ); + continue; + } + /* new material */ + if ( !_pico_stricmp( p->token,"newmtl" ) ) { + picoShader_t *shader; + char *name; + + /* get material name */ + name = _pico_parse( p,0 ); + + /* validate material name */ + if ( name == NULL || !strlen( name ) ) { + _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine ); + _obj_mtl_error_return; + } + /* create a new pico shader */ + shader = PicoNewShader( model ); + if ( shader == NULL ) { + _obj_mtl_error_return; + } + + /* set shader name */ + PicoSetShaderName( shader,name ); + + /* assign pointer to current shader */ + curShader = shader; + } + /* diffuse map name */ + else if ( !_pico_stricmp( p->token,"map_kd" ) ) { + char *mapName; + picoShader_t *shader; + + /* pointer to current shader must be valid */ + if ( curShader == NULL ) { + _obj_mtl_error_return; + } + + /* get material's diffuse map name */ + mapName = _pico_parse( p,0 ); + + /* validate map name */ + if ( mapName == NULL || !strlen( mapName ) ) { + _pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine ); + _obj_mtl_error_return; + } + /* create a new pico shader */ + shader = PicoNewShader( model ); + if ( shader == NULL ) { + _obj_mtl_error_return; + } + /* set shader map name */ + PicoSetShaderMapName( shader,mapName ); + } + /* dissolve factor (pseudo transparency 0..1) */ + /* where 0 means 100% transparent and 1 means opaque */ + else if ( !_pico_stricmp( p->token,"d" ) ) { + picoByte_t *diffuse; + float value; + + + /* get dissolve factor */ + if ( !_pico_parse_float( p,&value ) ) { + _obj_mtl_error_return; + } + + /* set shader transparency */ + PicoSetShaderTransparency( curShader,value ); + + /* get shader's diffuse color */ + diffuse = PicoGetShaderDiffuseColor( curShader ); + + /* set diffuse alpha to transparency */ + diffuse[ 3 ] = (picoByte_t)( value * 255.0 ); + + /* set shader's new diffuse color */ + PicoSetShaderDiffuseColor( curShader,diffuse ); + } + /* shininess (phong specular component) */ + else if ( !_pico_stricmp( p->token,"ns" ) ) { + /* remark: + * - well, this is some major obj spec fuckup once again. some + * apps store this in 0..1 range, others use 0..100 range, + * even others use 0..2048 range, and again others use the + * range 0..128, some even use 0..1000, 0..200, 400..700, + * honestly, what's up with the 3d app coders? happens when + * you smoke too much weed i guess. -sea + */ + float value; + + /* pointer to current shader must be valid */ + if ( curShader == NULL ) { + _obj_mtl_error_return; + } + + /* get totally screwed up shininess (a random value in fact ;) */ + if ( !_pico_parse_float( p,&value ) ) { + _obj_mtl_error_return; + } + + /* okay, there is no way to set this correctly, so we simply */ + /* try to guess a few ranges (most common ones i have seen) */ + + /* assume 0..2048 range */ + if ( value > 1000 ) { + value = 128.0 * ( value / 2048.0 ); + } + /* assume 0..1000 range */ + else if ( value > 200 ) { + value = 128.0 * ( value / 1000.0 ); + } + /* assume 0..200 range */ + else if ( value > 100 ) { + value = 128.0 * ( value / 200.0 ); + } + /* assume 0..100 range */ + else if ( value > 1 ) { + value = 128.0 * ( value / 100.0 ); + } + /* assume 0..1 range */ + else { + value *= 128.0; + } + /* negative shininess is bad (yes, i have seen it...) */ + if ( value < 0.0 ) { + value = 0.0; + } + + /* set the pico shininess value in range 0..127 */ + /* geez, .obj is such a mess... */ + PicoSetShaderShininess( curShader,value ); + } + /* kol0r ambient (wut teh fuk does "ka" stand for?) */ + else if ( !_pico_stricmp( p->token,"ka" ) ) { + picoColor_t color; + picoVec3_t v; + + /* pointer to current shader must be valid */ + if ( curShader == NULL ) { + _obj_mtl_error_return; + } + + /* get color vector */ + if ( !_pico_parse_vec( p,v ) ) { + _obj_mtl_error_return; + } + + /* scale to byte range */ + color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 ); + color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 ); + color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 ); + color[ 3 ] = (picoByte_t)( 255 ); + + /* set ambient color */ + PicoSetShaderAmbientColor( curShader,color ); + } + /* kol0r diffuse */ + else if ( !_pico_stricmp( p->token,"kd" ) ) { + picoColor_t color; + picoVec3_t v; + + /* pointer to current shader must be valid */ + if ( curShader == NULL ) { + _obj_mtl_error_return; + } + + /* get color vector */ + if ( !_pico_parse_vec( p,v ) ) { + _obj_mtl_error_return; + } + + /* scale to byte range */ + color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 ); + color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 ); + color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 ); + color[ 3 ] = (picoByte_t)( 255 ); + + /* set diffuse color */ + PicoSetShaderDiffuseColor( curShader,color ); + } + /* kol0r specular */ + else if ( !_pico_stricmp( p->token,"ks" ) ) { + picoColor_t color; + picoVec3_t v; + + /* pointer to current shader must be valid */ + if ( curShader == NULL ) { + _obj_mtl_error_return; + } + + /* get color vector */ + if ( !_pico_parse_vec( p,v ) ) { + _obj_mtl_error_return; + } + + /* scale to byte range */ + color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 ); + color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 ); + color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 ); + color[ 3 ] = (picoByte_t)( 255 ); + + /* set specular color */ + PicoSetShaderSpecularColor( curShader,color ); + } +#endif + /* skip rest of line */ + _pico_parse_skip_rest( p ); + } + + /* free parser, file buffer, and file name */ + _pico_free_parser( p ); + _pico_free_file( mtlBuffer ); + _pico_free( fileName ); + + /* return with success */ + return 1; +} + +/* _obj_load: + * loads a wavefront obj model file. + */ +static picoModel_t *_obj_load( PM_PARAMS_LOAD ){ + TObjVertexData *vertexData = NULL; + picoModel_t *model; + picoSurface_t *curSurface = NULL; + picoParser_t *p; + int allocated; + int entries; + int numVerts = 0; + int numNormals = 0; + int numUVs = 0; + int curVertex = 0; + int curFace = 0; + + int autoGroupNumber = 0; + char autoGroupNameBuf[64]; + +#define AUTO_GROUPNAME( namebuf ) \ + sprintf( namebuf, "__autogroup_%d", autoGroupNumber++ ) +#define NEW_SURFACE( name ) \ + { \ + picoSurface_t *newSurface; \ + /* allocate a pico surface */ \ + newSurface = PicoNewSurface( model ); \ + if ( newSurface == NULL ) { \ + _obj_error_return( "Error allocating surface" ); } \ + /* reset face index for surface */ \ + curFace = 0; \ + /* if we can, assign the previous shader to this surface */ \ + if ( curSurface ) { \ + PicoSetSurfaceShader( newSurface, curSurface->shader ); } \ + /* set ptr to current surface */ \ + curSurface = newSurface; \ + /* we use triangle meshes */ \ + PicoSetSurfaceType( newSurface,PICO_TRIANGLES ); \ + /* set surface name */ \ + PicoSetSurfaceName( newSurface,name ); \ + } + + /* helper */ +#define _obj_error_return( m ) \ + { \ + _pico_printf( PICO_ERROR, "%s in OBJ %s, line %d.", m, model->fileName, p->curLine ); \ + _pico_free_parser( p ); \ + FreeObjVertexData( vertexData ); \ + PicoFreeModel( model ); \ + return NULL; \ + } + /* alllocate a new pico parser */ + p = _pico_new_parser( (const 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 ); + + /* try loading the materials; we don't handle the result */ +#if 1 + _obj_mtl_load( model ); +#endif + + /* parse obj line by line */ + while ( 1 ) + { + /* get first token on line */ + if ( _pico_parse_first( p ) == NULL ) { + break; + } + + /* skip empty lines */ + if ( p->token == NULL || !strlen( p->token ) ) { + continue; + } + + /* skip comment lines */ + if ( p->token[0] == '#' ) { + _pico_parse_skip_rest( p ); + continue; + } + /* vertex */ + if ( !_pico_stricmp( p->token,"v" ) ) { + TObjVertexData *data; + picoVec3_t v; + + vertexData = SizeObjVertexData( vertexData,numVerts + 1,&entries,&allocated ); + if ( vertexData == NULL ) { + _obj_error_return( "Realloc of vertex data failed (1)" ); + } + + data = &vertexData[ numVerts++ ]; + + /* get and copy vertex */ + if ( !_pico_parse_vec( p,v ) ) { + _obj_error_return( "Vertex parse error" ); + } + + _pico_copy_vec( v,data->v ); + +#ifdef DEBUG_PM_OBJ_EX + printf( "Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2] ); +#endif + } + /* uv coord */ + else if ( !_pico_stricmp( p->token,"vt" ) ) { + TObjVertexData *data; + picoVec2_t coord; + + vertexData = SizeObjVertexData( vertexData,numUVs + 1,&entries,&allocated ); + if ( vertexData == NULL ) { + _obj_error_return( "Realloc of vertex data failed (2)" ); + } + + data = &vertexData[ numUVs++ ]; + + /* get and copy tex coord */ + if ( !_pico_parse_vec2( p,coord ) ) { + _obj_error_return( "UV coord parse error" ); + } + + _pico_copy_vec2( coord,data->vt ); + +#ifdef DEBUG_PM_OBJ_EX + printf( "TexCoord: u: %f v: %f\n",coord[0],coord[1] ); +#endif + } + /* vertex normal */ + else if ( !_pico_stricmp( p->token,"vn" ) ) { + TObjVertexData *data; + picoVec3_t n; + + vertexData = SizeObjVertexData( vertexData,numNormals + 1,&entries,&allocated ); + if ( vertexData == NULL ) { + _obj_error_return( "Realloc of vertex data failed (3)" ); + } + + data = &vertexData[ numNormals++ ]; + + /* get and copy vertex normal */ + if ( !_pico_parse_vec( p,n ) ) { + _obj_error_return( "Vertex normal parse error" ); + } + + _pico_copy_vec( n,data->vn ); + +#ifdef DEBUG_PM_OBJ_EX + printf( "Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2] ); +#endif + } + /* new group (for us this means a new surface) */ + else if ( !_pico_stricmp( p->token,"g" ) ) { + char *groupName; + + /* get first group name (ignore 2nd,3rd,etc.) */ + groupName = _pico_parse( p,0 ); + if ( groupName == NULL || !strlen( groupName ) ) { + /* some obj exporters feel like they don't need to */ + /* supply a group name. so we gotta handle it here */ +#if 1 + strcpy( p->token,"default" ); + groupName = p->token; +#else + _obj_error_return( "Invalid or missing group name" ); +#endif + } + + if ( curFace == 0 && curSurface != NULL ) { + PicoSetSurfaceName( curSurface,groupName ); + } + else + { + NEW_SURFACE( groupName ); + } + +#ifdef DEBUG_PM_OBJ_EX + printf( "Group: '%s'\n",groupName ); +#endif + } + /* face (oh jesus, hopefully this will do the job right ;) */ + else if ( !_pico_stricmp( p->token,"f" ) ) { + /* okay, this is a mess. some 3d apps seem to try being unique, */ + /* hello cinema4d & 3d exploration, feel good today?, and save */ + /* this crap in tons of different formats. gah, those screwed */ + /* coders. tho the wavefront obj standard defines exactly two */ + /* ways of storing face information. so, i really won't support */ + /* such stupid extravaganza here! */ + + picoVec3_t verts [ 4 ]; + picoVec3_t normals[ 4 ]; + picoVec2_t coords [ 4 ]; + + int iv [ 4 ], has_v; + int ivt[ 4 ], has_vt = 0; + int ivn[ 4 ], has_vn = 0; + int have_quad = 0; + int slashcount = 0; + int doubleslash = 0; + int i; + + if ( curSurface == NULL ) { + _pico_printf( PICO_WARNING,"No group defined for faces, so creating an autoSurface in OBJ, line %d.",p->curLine ); + AUTO_GROUPNAME( autoGroupNameBuf ); + NEW_SURFACE( autoGroupNameBuf ); + } + + /* group defs *must* come before faces */ + if ( curSurface == NULL ) { + _obj_error_return( "No group defined for faces" ); + } + +#ifdef DEBUG_PM_OBJ_EX + printf( "Face: " ); +#endif + /* read vertex/uv/normal indices for the first three face */ + /* vertices (cause we only support triangles) into 'i*[]' */ + /* store the actual vertex/uv/normal data in three arrays */ + /* called 'verts','coords' and 'normals'. */ + for ( i = 0; i < 4; i++ ) + { + char *str; + + /* get next vertex index string (different */ + /* formats are handled below) */ + str = _pico_parse( p,0 ); + if ( str == NULL ) { + /* just break for quads */ + if ( i == 3 ) { + break; + } + + /* error otherwise */ + _obj_error_return( "Face parse error" ); + } + /* if this is the fourth index string we're */ + /* parsing we assume that we have a quad */ + if ( i == 3 ) { + have_quad = 1; + } + + /* get slash count once */ + if ( i == 0 ) { + slashcount = _pico_strchcount( str,'/' ); + doubleslash = strstr( str,"//" ) != NULL; + } + /* handle format 'v//vn' */ + if ( doubleslash && ( slashcount == 2 ) ) { + has_v = has_vn = 1; + sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] ); + } + /* handle format 'v/vt/vn' */ + else if ( !doubleslash && ( slashcount == 2 ) ) { + has_v = has_vt = has_vn = 1; + sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] ); + } + /* handle format 'v/vt' (non-standard fuckage) */ + else if ( !doubleslash && ( slashcount == 1 ) ) { + has_v = has_vt = 1; + sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] ); + } + /* else assume face format 'v' */ + /* (must have been invented by some bored granny) */ + else { + /* get single vertex index */ + has_v = 1; + iv[ i ] = atoi( str ); + + /* either invalid face format or out of range */ + if ( iv[ i ] == 0 ) { + _obj_error_return( "Invalid face format" ); + } + } + /* fix useless back references */ + /* todo: check if this works as it is supposed to */ + + /* assign new indices */ + if ( iv [ i ] < 0 ) { + iv [ i ] = ( numVerts - iv [ i ] ); + } + if ( ivt[ i ] < 0 ) { + ivt[ i ] = ( numUVs - ivt[ i ] ); + } + if ( ivn[ i ] < 0 ) { + ivn[ i ] = ( numNormals - ivn[ i ] ); + } + + /* validate indices */ + /* - commented out. index range checks will trigger + if (iv [ i ] < 1) iv [ i ] = 1; + if (ivt[ i ] < 1) ivt[ i ] = 1; + if (ivn[ i ] < 1) ivn[ i ] = 1; + */ + /* set vertex origin */ + if ( has_v ) { + /* check vertex index range */ + if ( iv[ i ] < 1 || iv[ i ] > numVerts ) { + _obj_error_return( "Vertex index out of range" ); + } + + /* get vertex data */ + verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ]; + verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ]; + verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ]; + } + /* set vertex normal */ + if ( has_vn ) { + /* check normal index range */ + if ( ivn[ i ] < 1 || ivn[ i ] > numNormals ) { + _obj_error_return( "Normal index out of range" ); + } + + /* get normal data */ + normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ]; + normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ]; + normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ]; + } + /* set texture coordinate */ + if ( has_vt ) { + /* check uv index range */ + if ( ivt[ i ] < 1 || ivt[ i ] > numUVs ) { + _obj_error_return( "UV coord index out of range" ); + } + + /* get uv coord data */ + coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ]; + coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ]; + coords[ i ][ 1 ] = -coords[ i ][ 1 ]; + } +#ifdef DEBUG_PM_OBJ_EX + printf( "(%4d",iv[ i ] ); + if ( has_vt ) { + printf( " %4d",ivt[ i ] ); + } + if ( has_vn ) { + printf( " %4d",ivn[ i ] ); + } + printf( ") " ); +#endif + } +#ifdef DEBUG_PM_OBJ_EX + printf( "\n" ); +#endif + /* now that we have extracted all the indices and have */ + /* read the actual data we need to assign all the crap */ + /* to our current pico surface */ + if ( has_v ) { + int max = 3; + if ( have_quad ) { + max = 4; + } + + /* assign all surface information */ + for ( i = 0; i < max; i++ ) + { + /*if( has_v )*/ PicoSetSurfaceXYZ( curSurface, ( curVertex + i ), verts [ i ] ); + /*if( has_vt )*/ PicoSetSurfaceST( curSurface,0,( curVertex + i ), coords [ i ] ); + /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface, ( curVertex + i ), normals[ i ] ); + } + /* add our triangle (A B C) */ + PicoSetSurfaceIndex( curSurface,( curFace * 3 + 2 ),(picoIndex_t)( curVertex + 0 ) ); + PicoSetSurfaceIndex( curSurface,( curFace * 3 + 1 ),(picoIndex_t)( curVertex + 1 ) ); + PicoSetSurfaceIndex( curSurface,( curFace * 3 + 0 ),(picoIndex_t)( curVertex + 2 ) ); + curFace++; + + /* if we don't have a simple triangle, but a quad... */ + if ( have_quad ) { + /* we have to add another triangle (2nd half of quad which is A C D) */ + PicoSetSurfaceIndex( curSurface,( curFace * 3 + 2 ),(picoIndex_t)( curVertex + 0 ) ); + PicoSetSurfaceIndex( curSurface,( curFace * 3 + 1 ),(picoIndex_t)( curVertex + 2 ) ); + PicoSetSurfaceIndex( curSurface,( curFace * 3 + 0 ),(picoIndex_t)( curVertex + 3 ) ); + curFace++; + } + /* increase vertex count */ + curVertex += max; + } + } + else if ( !_pico_stricmp( p->token,"usemtl" ) ) { + picoShader_t *shader; + char *name; + + /* get material name */ + name = _pico_parse( p,0 ); + + if ( curFace != 0 || curSurface == NULL ) { + _pico_printf( PICO_WARNING,"No group defined for usemtl, so creating an autoSurface in OBJ, line %d.",p->curLine ); + AUTO_GROUPNAME( autoGroupNameBuf ); + NEW_SURFACE( autoGroupNameBuf ); + } + + /* validate material name */ + if ( name == NULL || !strlen( name ) ) { + _pico_printf( PICO_ERROR,"Missing material name in OBJ, line %d.",p->curLine ); + } + else + { + shader = PicoFindShader( model, name, 1 ); + if ( shader == NULL ) { + _pico_printf( PICO_WARNING,"Undefined material name in OBJ, line %d. Making a default shader.",p->curLine ); + + /* create a new pico shader */ + shader = PicoNewShader( model ); + if ( shader != NULL ) { + PicoSetShaderName( shader,name ); + PicoSetShaderMapName( shader,name ); + PicoSetSurfaceShader( curSurface, shader ); + } + } + else + { + PicoSetSurfaceShader( curSurface, shader ); + } + } + } + /* skip unparsed rest of line and continue */ + _pico_parse_skip_rest( p ); + } + /* free memory used by temporary vertexdata */ + FreeObjVertexData( vertexData ); + + /* return allocated pico model */ + return model; +// return NULL; +} + +/* pico file format module definition */ +const picoModule_t picoModuleOBJ = +{ + "0.6-b", /* module version string */ + "Wavefront ASCII", /* module display name */ + "seaw0lf", /* author's name */ + "2002 seaw0lf", /* module copyright */ + { + "obj",NULL,NULL,NULL /* default extensions to use */ + }, + _obj_canload, /* validation routine */ + _obj_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_terrain.c b/libs/picomodel/pm_terrain.c new file mode 100644 index 0000000..a82e4b1 --- /dev/null +++ b/libs/picomodel/pm_terrain.c @@ -0,0 +1,594 @@ +/* ----------------------------------------------------------------------------- + + PicoModel Library + + Copyright (c) 2003, 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. + + ----------------------------------------------------------------------------- */ + +/* dependencies */ +#include "picointernal.h" + + + +typedef struct tga_s +{ + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} +tga_t; + + + +/* + _terrain_load_tga_buffer() + loads a tga image into a newly allocated image buffer + fixme: replace/clean this function + */ + +void _terrain_load_tga_buffer( unsigned char *buffer, unsigned char **pic, int *width, int *height ) { + int row, column; + int columns, rows, numPixels; + unsigned char *pixbuf; + unsigned char *buf_p; + tga_t targa_header; + unsigned char *targa_rgba; + + + *pic = NULL; + + if ( buffer == NULL ) { + return; + } + + buf_p = buffer; + + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = _pico_little_short( *(short*)buf_p ); + buf_p += 2; + targa_header.colormap_length = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.y_origin = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.width = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.height = _pico_little_short( *(short*) buf_p ); + buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + if ( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) { + _pico_printf( PICO_ERROR, "Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n" ); + pic = NULL; + return; + } + + if ( targa_header.colormap_type != 0 ) { + _pico_printf( PICO_ERROR, "Indexed color TGA images not supported\n" ); + return; + } + + if ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 && targa_header.image_type != 3 ) { + _pico_printf( PICO_ERROR, "Only 32 or 24 bit TGA images supported (not indexed color)\n" ); + pic = NULL; + return; + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if ( width ) { + *width = columns; + } + if ( height ) { + *height = rows; + } + + targa_rgba = _pico_alloc( numPixels * 4 ); + *pic = targa_rgba; + + if ( targa_header.id_length != 0 ) { + buf_p += targa_header.id_length; // skip TARGA image comment + + } + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) { + // Uncompressed RGB or gray scale image + for ( row = rows - 1; row >= 0; row-- ) + { + pixbuf = targa_rgba + row * columns * 4; + for ( column = 0; column < columns; column++ ) + { + unsigned char red,green,blue,alphabyte; + switch ( targa_header.pixel_size ) + { + + case 8: + blue = *buf_p++; + green = blue; + red = blue; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + break; + } + } + } + } + + /* rle encoded pixels */ + else if ( targa_header.image_type == 10 ) { + unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j; + + red = 0; + green = 0; + blue = 0; + alphabyte = 0xff; + + for ( row = rows - 1; row >= 0; row-- ) { + pixbuf = targa_rgba + row * columns * 4; + for ( column = 0; column < columns; ) { + packetHeader = *buf_p++; + packetSize = 1 + ( packetHeader & 0x7f ); + if ( packetHeader & 0x80 ) { // run-length packet + switch ( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + break; + default: + //Error("LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + break; + } + + for ( j = 0; j < packetSize; j++ ) { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + column++; + if ( column == columns ) { // run spans across rows + column = 0; + if ( row > 0 ) { + row--; + } + else{ + goto breakOut; + } + pixbuf = targa_rgba + row * columns * 4; + } + } + } + else { // non run-length packet + for ( j = 0; j < packetSize; j++ ) { + switch ( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + //Sysprintf("LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name ); + break; + } + column++; + if ( column == columns ) { // pixel packet run spans across rows + column = 0; + if ( row > 0 ) { + row--; + } + else{ + goto breakOut; + } + pixbuf = targa_rgba + row * columns * 4; + } + } + } + } +breakOut:; + } + } + + /* fix vertically flipped image */ + if ( ( targa_header.attributes & ( 1 << 5 ) ) ) { + int flip; + for ( row = 0; row < .5f * rows; row++ ) + { + for ( column = 0; column < columns; column++ ) + { + flip = *( (int*)targa_rgba + row * columns + column ); + *( (int*)targa_rgba + row * columns + column ) = *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ); + *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ) = flip; + } + } + } +} + + + +/* + _terrain_canload() + validates a picoterrain file + */ + +static int _terrain_canload( PM_PARAMS_CANLOAD ) { + picoParser_t *p; + + + /* create pico parser */ + p = _pico_new_parser( (const 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, "picoterrain" ) ) { + _pico_free_parser( p ); + return PICO_PMV_ERROR_IDENT; + } + + /* free the pico parser object */ + _pico_free_parser( p ); + + /* file seems to be a valid picoterrain file */ + return PICO_PMV_OK; +} + + + +/* + _terrain_load() + loads a picoterrain file + */ + +static picoModel_t *_terrain_load( PM_PARAMS_LOAD ) { + int i, j, v, pw[ 5 ], r; + picoParser_t *p; + + char *shader, *heightmapFile, *colormapFile; + picoVec3_t scale, origin; + + unsigned char *imageBuffer; + int imageBufSize, w, h, cw, ch; + unsigned char *heightmap, *colormap, *heightPixel, *colorPixel; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* create pico parser */ + p = _pico_new_parser( (const picoByte_t*) buffer, bufSize ); + if ( p == NULL ) { + return NULL; + } + + /* get first token */ + if ( _pico_parse_first( p ) == NULL ) { + return NULL; + } + + /* check first token */ + if ( _pico_stricmp( p->token, "picoterrain" ) ) { + _pico_printf( PICO_ERROR, "Invalid PicoTerrain model" ); + _pico_free_parser( p ); + return NULL; + } + + /* setup */ + shader = heightmapFile = colormapFile = NULL; + _pico_set_vec( scale, 512, 512, 32 ); + + /* parse ase model file */ + while ( 1 ) + { + /* get first token on line */ + if ( !_pico_parse_first( p ) ) { + break; + } + + /* skip empty lines */ + if ( !p->token || !p->token[ 0 ] ) { + continue; + } + + /* shader */ + if ( !_pico_stricmp( p->token, "shader" ) ) { + if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { + if ( shader != NULL ) { + _pico_free( shader ); + } + shader = _pico_clone_alloc( p->token ); + } + } + + /* heightmap */ + else if ( !_pico_stricmp( p->token, "heightmap" ) ) { + if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { + if ( heightmapFile != NULL ) { + _pico_free( heightmapFile ); + } + heightmapFile = _pico_clone_alloc( p->token ); + } + } + + /* colormap */ + else if ( !_pico_stricmp( p->token, "colormap" ) ) { + if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { + if ( colormapFile != NULL ) { + _pico_free( colormapFile ); + } + colormapFile = _pico_clone_alloc( p->token ); + } + } + + /* scale */ + else if ( !_pico_stricmp( p->token, "scale" ) ) { + _pico_parse_vec( p, scale ); + } + + /* skip unparsed rest of line and continue */ + _pico_parse_skip_rest( p ); + } + + /* ----------------------------------------------------------------- */ + + /* load heightmap */ + heightmap = imageBuffer = NULL; + _pico_load_file( heightmapFile, &imageBuffer, &imageBufSize ); + _terrain_load_tga_buffer( imageBuffer, &heightmap, &w, &h ); + _pico_free( heightmapFile ); + _pico_free_file( imageBuffer ); + + if ( heightmap == NULL || w < 2 || h < 2 ) { + _pico_printf( PICO_ERROR, "PicoTerrain model with invalid heightmap" ); + if ( shader != NULL ) { + _pico_free( shader ); + } + if ( colormapFile != NULL ) { + _pico_free( colormapFile ); + } + _pico_free_parser( p ); + return NULL; + } + + /* set origin (bottom lowest corner of terrain mesh) */ + _pico_set_vec( origin, ( w / -2 ) * scale[ 0 ], ( h / -2 ) * scale[ 1 ], -128 * scale[ 2 ] ); + + /* load colormap */ + colormap = imageBuffer = NULL; + _pico_load_file( colormapFile, &imageBuffer, &imageBufSize ); + _terrain_load_tga_buffer( imageBuffer, &colormap, &cw, &ch ); + _pico_free( colormapFile ); + _pico_free_file( imageBuffer ); + + if ( cw != w || ch != h ) { + _pico_printf( PICO_WARNING, "PicoTerrain colormap/heightmap size mismatch" ); + _pico_free( colormap ); + colormap = NULL; + } + + /* ----------------------------------------------------------------- */ + + /* 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, 1 ); /* 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 ); /* sea */ + return NULL; + } + + /* terrain surfaces are triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, "picoterrain" ); + + /* create new pico shader */ + picoShader = PicoNewShader( picoModel ); + if ( picoShader == NULL ) { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + _pico_free( shader ); + return NULL; + } + + /* detox and set shader name */ + _pico_setfext( shader, "" ); + _pico_unixify( shader ); + PicoSetShaderName( picoShader, shader ); + _pico_free( shader ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* make bogus normal */ + _pico_set_vec( normal, 0.0f, 0.0f, 0.0f ); + + /* create mesh */ + for ( j = 0; j < h; j++ ) + { + for ( i = 0; i < w; i++ ) + { + /* get pointers */ + v = i + ( j * w ); + heightPixel = heightmap + v * 4; + colorPixel = colormap + ? colormap + v * 4 + : NULL; + + /* set xyz */ + _pico_set_vec( xyz, origin[ 0 ] + scale[ 0 ] * i, + origin[ 1 ] + scale[ 1 ] * j, + origin[ 2 ] + scale[ 2 ] * heightPixel[ 0 ] ); + PicoSetSurfaceXYZ( picoSurface, v, xyz ); + + /* set normal */ + PicoSetSurfaceNormal( picoSurface, v, normal ); + + /* set st */ + st[ 0 ] = (float) i; + st[ 1 ] = (float) j; + PicoSetSurfaceST( picoSurface, 0, v, st ); + + /* set color */ + if ( colorPixel != NULL ) { + _pico_set_color( color, colorPixel[ 0 ], colorPixel[ 1 ], colorPixel[ 2 ], colorPixel[ 3 ] ); + } + else{ + _pico_set_color( color, 255, 255, 255, 255 ); + } + PicoSetSurfaceColor( picoSurface, 0, v, color ); + + /* set triangles (zero alpha in heightmap suppresses this quad) */ + if ( i < ( w - 1 ) && j < ( h - 1 ) && heightPixel[ 3 ] >= 128 ) { + /* set indexes */ + pw[ 0 ] = i + ( j * w ); + pw[ 1 ] = i + ( ( j + 1 ) * w ); + pw[ 2 ] = i + 1 + ( ( j + 1 ) * w ); + pw[ 3 ] = i + 1 + ( j * w ); + pw[ 4 ] = i + ( j * w ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( i + j ) & 1; + + /* make first triangle */ + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 0 ), (picoIndex_t) pw[ r + 0 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 1 ), (picoIndex_t) pw[ r + 1 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 2 ), (picoIndex_t) pw[ r + 2 ] ); + + /* make second triangle */ + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 3 ), (picoIndex_t) pw[ r + 0 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 4 ), (picoIndex_t) pw[ r + 2 ] ); + PicoSetSurfaceIndex( picoSurface, ( v * 6 + 5 ), (picoIndex_t) pw[ r + 3 ] ); + } + } + } + + /* free stuff */ + _pico_free_parser( p ); + _pico_free( heightmap ); + _pico_free( colormap ); + + /* return the new pico model */ + return picoModel; +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleTerrain = +{ + "1.3", /* module version string */ + "PicoTerrain", /* module display name */ + "Randy Reddig", /* author's name */ + "2003 Randy Reddig", /* module copyright */ + { + "picoterrain", NULL, NULL, NULL /* default extensions to use */ + }, + _terrain_canload, /* validation routine */ + _terrain_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/pivot.h b/libs/pivot.h new file mode 100644 index 0000000..93cb354 --- /dev/null +++ b/libs/pivot.h @@ -0,0 +1,284 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_PIVOT_H ) +#define INCLUDED_PIVOT_H + +#include "math/matrix.h" + + +inline void billboard_viewplaneOriented( Matrix4& rotation, const Matrix4& world2screen ){ +#if 1 + rotation = g_matrix4_identity; + Vector3 x( vector3_normalised( vector4_to_vector3( world2screen.x() ) ) ); + Vector3 y( vector3_normalised( vector4_to_vector3( world2screen.y() ) ) ); + Vector3 z( vector3_normalised( vector4_to_vector3( world2screen.z() ) ) ); + vector4_to_vector3( rotation.y() ) = Vector3( x.y(), y.y(), z.y() ); + vector4_to_vector3( rotation.z() ) = vector3_negated( Vector3( x.z(), y.z(), z.z() ) ); + vector4_to_vector3( rotation.x() ) = vector3_normalised( vector3_cross( vector4_to_vector3( rotation.y() ), vector4_to_vector3( rotation.z() ) ) ); + vector4_to_vector3( rotation.y() ) = vector3_cross( vector4_to_vector3( rotation.z() ), vector4_to_vector3( rotation.x() ) ); +#else + Matrix4 screen2world( matrix4_full_inverse( world2screen ) ); + + Vector3 near_( + vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4( 0, 0, -1, 1 ) + ) + ) + ); + + Vector3 far_( + vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4( 0, 0, 1, 1 ) + ) + ) + ); + + Vector3 up( + vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4( 0, 1, -1, 1 ) + ) + ) + ); + + rotation = g_matrix4_identity; + vector4_to_vector3( rotation.y() ) = vector3_normalised( vector3_subtracted( up, near_ ) ); + vector4_to_vector3( rotation.z() ) = vector3_normalised( vector3_subtracted( near_, far_ ) ); + vector4_to_vector3( rotation.x() ) = vector3_normalised( vector3_cross( vector4_to_vector3( rotation.y() ), vector4_to_vector3( rotation.z() ) ) ); + vector4_to_vector3( rotation.y() ) = vector3_cross( vector4_to_vector3( rotation.z() ), vector4_to_vector3( rotation.x() ) ); +#endif +} + +inline void billboard_viewpointOriented( Matrix4& rotation, const Matrix4& world2screen ){ + Matrix4 screen2world( matrix4_full_inverse( world2screen ) ); + +#if 1 + rotation = g_matrix4_identity; + vector4_to_vector3( rotation.y() ) = vector3_normalised( vector4_to_vector3( screen2world.y() ) ); + vector4_to_vector3( rotation.z() ) = vector3_negated( vector3_normalised( vector4_to_vector3( screen2world.z() ) ) ); + vector4_to_vector3( rotation.x() ) = vector3_normalised( vector3_cross( vector4_to_vector3( rotation.y() ), vector4_to_vector3( rotation.z() ) ) ); + vector4_to_vector3( rotation.y() ) = vector3_cross( vector4_to_vector3( rotation.z() ), vector4_to_vector3( rotation.x() ) ); +#else + Vector3 near_( + vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4( world2screen[12] / world2screen[15], world2screen[13] / world2screen[15], -1, 1 ) + ) + ) + ); + + Vector3 far_( + vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4( world2screen[12] / world2screen[15], world2screen[13] / world2screen[15], 1, 1 ) + ) + ) + ); + + Vector3 up( + vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4( world2screen[12] / world2screen[15], world2screen[13] / world2screen[15] + 1, -1, 1 ) + ) + ) + ); + + rotation = g_matrix4_identity; + vector4_to_vector3( rotation.y() ) = vector3_normalised( vector3_subtracted( up, near_ ) ); + vector4_to_vector3( rotation.z() ) = vector3_normalised( vector3_subtracted( near_, far_ ) ); + vector4_to_vector3( rotation.x() ) = vector3_normalised( vector3_cross( vector4_to_vector3( rotation.y() ), vector4_to_vector3( rotation.z() ) ) ); + vector4_to_vector3( rotation.y() ) = vector3_cross( vector4_to_vector3( rotation.z() ), vector4_to_vector3( rotation.x() ) ); +#endif +} + + +inline void ConstructObject2Screen( Matrix4& object2screen, const Matrix4& object2world, const Matrix4& world2view, const Matrix4& view2device, const Matrix4& device2screen ){ + object2screen = device2screen; + matrix4_multiply_by_matrix4( object2screen, view2device ); + matrix4_multiply_by_matrix4( object2screen, world2view ); + matrix4_multiply_by_matrix4( object2screen, object2world ); +} + +inline void ConstructObject2Device( Matrix4& object2screen, const Matrix4& object2world, const Matrix4& world2view, const Matrix4& view2device ){ + object2screen = view2device; + matrix4_multiply_by_matrix4( object2screen, world2view ); + matrix4_multiply_by_matrix4( object2screen, object2world ); +} + +inline void ConstructDevice2Object( Matrix4& device2object, const Matrix4& object2world, const Matrix4& world2view, const Matrix4& view2device ){ + ConstructObject2Device( device2object, object2world, world2view, view2device ); + matrix4_full_invert( device2object ); +} + +//! S = ( Inverse(Object2Screen *post ScaleOf(Object2Screen) ) *post Object2Screen +inline void pivot_scale( Matrix4& scale, const Matrix4& pivot2screen ){ + Matrix4 pre_scale( g_matrix4_identity ); + pre_scale[0] = static_cast( vector3_length( vector4_to_vector3( pivot2screen.x() ) ) ); + pre_scale[5] = static_cast( vector3_length( vector4_to_vector3( pivot2screen.y() ) ) ); + pre_scale[10] = static_cast( vector3_length( vector4_to_vector3( pivot2screen.z() ) ) ); + + scale = pivot2screen; + matrix4_multiply_by_matrix4( scale, pre_scale ); + matrix4_full_invert( scale ); + matrix4_multiply_by_matrix4( scale, pivot2screen ); +} + +// scale by (inverse) W +inline void pivot_perspective( Matrix4& scale, const Matrix4& pivot2screen ){ + scale = g_matrix4_identity; + scale[0] = scale[5] = scale[10] = pivot2screen[15]; +} + +inline void ConstructDevice2Manip( Matrix4& device2manip, const Matrix4& object2world, const Matrix4& world2view, const Matrix4& view2device, const Matrix4& device2screen ){ + Matrix4 pivot2screen; + ConstructObject2Screen( pivot2screen, object2world, world2view, view2device, device2screen ); + + ConstructObject2Device( device2manip, object2world, world2view, view2device ); + + Matrix4 scale; + pivot_scale( scale, pivot2screen ); + matrix4_multiply_by_matrix4( device2manip, scale ); + pivot_perspective( scale, pivot2screen ); + matrix4_multiply_by_matrix4( device2manip, scale ); + + matrix4_full_invert( device2manip ); +} + +inline void Pivot2World_worldSpace( Matrix4& manip2world, const Matrix4& pivot2world, const Matrix4& modelview, const Matrix4& projection, const Matrix4& viewport ){ + manip2world = pivot2world; + + Matrix4 pivot2screen; + ConstructObject2Screen( pivot2screen, pivot2world, modelview, projection, viewport ); + + Matrix4 scale; + pivot_scale( scale, pivot2screen ); + matrix4_multiply_by_matrix4( manip2world, scale ); + pivot_perspective( scale, pivot2screen ); + matrix4_multiply_by_matrix4( manip2world, scale ); +} + +inline void Pivot2World_viewpointSpace( Matrix4& manip2world, Vector3& axis, const Matrix4& pivot2world, const Matrix4& modelview, const Matrix4& projection, const Matrix4& viewport ){ + manip2world = pivot2world; + + Matrix4 pivot2screen; + ConstructObject2Screen( pivot2screen, pivot2world, modelview, projection, viewport ); + + Matrix4 scale; + pivot_scale( scale, pivot2screen ); + matrix4_multiply_by_matrix4( manip2world, scale ); + + billboard_viewpointOriented( scale, pivot2screen ); + axis = vector4_to_vector3( scale.z() ); + matrix4_multiply_by_matrix4( manip2world, scale ); + + pivot_perspective( scale, pivot2screen ); + matrix4_multiply_by_matrix4( manip2world, scale ); +} + +inline void Pivot2World_viewplaneSpace( Matrix4& manip2world, const Matrix4& pivot2world, const Matrix4& modelview, const Matrix4& projection, const Matrix4& viewport ){ + manip2world = pivot2world; + + Matrix4 pivot2screen; + ConstructObject2Screen( pivot2screen, pivot2world, modelview, projection, viewport ); + + Matrix4 scale; + pivot_scale( scale, pivot2screen ); + matrix4_multiply_by_matrix4( manip2world, scale ); + + billboard_viewplaneOriented( scale, pivot2screen ); + matrix4_multiply_by_matrix4( manip2world, scale ); + + pivot_perspective( scale, pivot2screen ); + matrix4_multiply_by_matrix4( manip2world, scale ); +} + + +#include "renderable.h" +#include "cullable.h" +#include "render.h" + +const Colour4b g_colour_x( 255, 0, 0, 255 ); +const Colour4b g_colour_y( 0, 255, 0, 255 ); +const Colour4b g_colour_z( 0, 0, 255, 255 ); + +class Shader; + +class RenderablePivot : public OpenGLRenderable +{ +VertexBuffer m_vertices; +public: +mutable Matrix4 m_localToWorld; +typedef Static StaticShader; +static Shader* getShader(){ + return StaticShader::instance(); +} + +RenderablePivot(){ + m_vertices.reserve( 6 ); + + m_vertices.push_back( PointVertex( Vertex3f( 0, 0, 0 ), g_colour_x ) ); + m_vertices.push_back( PointVertex( Vertex3f( 16, 0, 0 ), g_colour_x ) ); + + m_vertices.push_back( PointVertex( Vertex3f( 0, 0, 0 ), g_colour_y ) ); + m_vertices.push_back( PointVertex( Vertex3f( 0, 16, 0 ), g_colour_y ) ); + + m_vertices.push_back( PointVertex( Vertex3f( 0, 0, 0 ), g_colour_z ) ); + m_vertices.push_back( PointVertex( Vertex3f( 0, 0, 16 ), g_colour_z ) ); +} + +void render( RenderStateFlags state ) const { + if ( m_vertices.size() == 0 ) { + return; + } + if ( m_vertices.data() == 0 ) { + return; + } + glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &m_vertices.data()->vertex ); + glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &m_vertices.data()->colour ); + glDrawArrays( GL_LINES, 0, m_vertices.size() ); +} + +void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld ) const { + renderer.PushState(); + + Pivot2World_worldSpace( m_localToWorld, localToWorld, volume.GetModelview(), volume.GetProjection(), volume.GetViewport() ); + + renderer.Highlight( Renderer::ePrimitive, false ); + renderer.SetState( getShader(), Renderer::eWireframeOnly ); + renderer.SetState( getShader(), Renderer::eFullMaterials ); + renderer.addRenderable( *this, m_localToWorld ); + + renderer.PopState(); +} +}; + + + +#endif diff --git a/libs/profile/CMakeLists.txt b/libs/profile/CMakeLists.txt new file mode 100644 index 0000000..e3822f4 --- /dev/null +++ b/libs/profile/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(profile + file.cpp file.h + profile.cpp profile.h + ) diff --git a/libs/profile/file.cpp b/libs/profile/file.cpp new file mode 100644 index 0000000..9f83393 --- /dev/null +++ b/libs/profile/file.cpp @@ -0,0 +1,376 @@ +/* + 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. + */ + +// +// File class, can be a memory file or a regular disk file. +// Originally from LeoCAD, used with permission from the author. :) +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "file.h" + +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////// +// File construction/destruction + +MemStream::MemStream(){ + m_nGrowBytes = 1024; + m_nPosition = 0; + m_nBufferSize = 0; + m_nFileSize = 0; + m_pBuffer = NULL; + m_bAutoDelete = true; +} + +MemStream::MemStream( size_type nLen ){ + m_nGrowBytes = 1024; + m_nPosition = 0; + m_nBufferSize = 0; + m_nFileSize = 0; + m_pBuffer = NULL; + m_bAutoDelete = true; + + GrowFile( nLen ); +} + +FileStream::FileStream(){ + m_hFile = NULL; + m_bCloseOnDelete = false; +} + +MemStream::~MemStream(){ + if ( m_pBuffer ) { + Close(); + } + + m_nGrowBytes = 0; + m_nPosition = 0; + m_nBufferSize = 0; + m_nFileSize = 0; +} + +FileStream::~FileStream(){ + if ( m_hFile != NULL && m_bCloseOnDelete ) { + Close(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// File operations + +char* MemStream::ReadString( char* pBuf, size_type nMax ){ + int nRead = 0; + unsigned char ch; + + if ( nMax <= 0 ) { + return NULL; + } + if ( m_nPosition >= m_nFileSize ) { + return NULL; + } + + while ( ( --nMax ) ) + { + if ( m_nPosition == m_nFileSize ) { + break; + } + + ch = m_pBuffer[m_nPosition]; + m_nPosition++; + pBuf[nRead++] = ch; + + if ( ch == '\n' ) { + break; + } + } + + pBuf[nRead] = '\0'; + return pBuf; +} + +char* FileStream::ReadString( char* pBuf, size_type nMax ){ + return fgets( pBuf, static_cast( nMax ), m_hFile ); +} + +MemStream::size_type MemStream::read( byte_type* buffer, size_type length ){ + if ( length == 0 ) { + return 0; + } + + if ( m_nPosition > m_nFileSize ) { + return 0; + } + + size_type nRead; + if ( m_nPosition + length > m_nFileSize ) { + nRead = m_nFileSize - m_nPosition; + } + else{ + nRead = length; + } + + memcpy( (unsigned char*)buffer, (unsigned char*)m_pBuffer + m_nPosition, nRead ); + m_nPosition += nRead; + + return nRead; +} + +FileStream::size_type FileStream::read( byte_type* buffer, size_type length ){ + return fread( buffer, 1, length, m_hFile ); +} + +int MemStream::GetChar(){ + if ( m_nPosition > m_nFileSize ) { + return 0; + } + + unsigned char* ret = (unsigned char*)m_pBuffer + m_nPosition; + m_nPosition++; + + return *ret; +} + +int FileStream::GetChar(){ + return fgetc( m_hFile ); +} + +MemStream::size_type MemStream::write( const byte_type* buffer, size_type length ){ + if ( length == 0 ) { + return 0; + } + + if ( m_nPosition + length > m_nBufferSize ) { + GrowFile( m_nPosition + length ); + } + + memcpy( (unsigned char*)m_pBuffer + m_nPosition, (unsigned char*)buffer, length ); + + m_nPosition += size_type( length ); + + if ( m_nPosition > m_nFileSize ) { + m_nFileSize = m_nPosition; + } + + return length; +} + +FileStream::size_type FileStream::write( const byte_type* buffer, size_type length ){ + return fwrite( buffer, 1, length, m_hFile ); +} + +int MemStream::PutChar( int c ){ + if ( m_nPosition + 1 > m_nBufferSize ) { + GrowFile( m_nPosition + 1 ); + } + + unsigned char* bt = (unsigned char*)m_pBuffer + m_nPosition; + *bt = c; + + m_nPosition++; + + if ( m_nPosition > m_nFileSize ) { + m_nFileSize = m_nPosition; + } + + return 1; +} + +/*!\todo SPoG suggestion: replace printf with operator >> using c++ iostream and strstream */ +void FileStream::printf( const char* s, ... ){ + va_list args; + + va_start( args, s ); + vfprintf( m_hFile, s, args ); + va_end( args ); +} + +/*!\todo SPoG suggestion: replace printf with operator >> using c++ iostream and strstream */ +void MemStream::printf( const char* s, ... ){ + va_list args; + + char buffer[4096]; + va_start( args, s ); + vsprintf( buffer, s, args ); + va_end( args ); + write( reinterpret_cast( buffer ), strlen( buffer ) ); +} + +int FileStream::PutChar( int c ){ + return fputc( c, m_hFile ); +} + +bool FileStream::Open( const char *filename, const char *mode ){ + m_hFile = fopen( filename, mode ); + m_bCloseOnDelete = true; + + return ( m_hFile != NULL ); +} + +void MemStream::Close(){ + m_nGrowBytes = 0; + m_nPosition = 0; + m_nBufferSize = 0; + m_nFileSize = 0; + if ( m_pBuffer && m_bAutoDelete ) { + free( m_pBuffer ); + } + m_pBuffer = NULL; +} + +void FileStream::Close(){ + if ( m_hFile != NULL ) { + fclose( m_hFile ); + } + + m_hFile = NULL; + m_bCloseOnDelete = false; +} + +int MemStream::Seek( offset_type lOff, int nFrom ){ + size_type lNewPos = m_nPosition; + + if ( nFrom == SEEK_SET ) { + lNewPos = lOff; + } + else if ( nFrom == SEEK_CUR ) { + lNewPos += lOff; + } + else if ( nFrom == SEEK_END ) { + lNewPos = m_nFileSize + lOff; + } + else{ + return -1; + } + + m_nPosition = lNewPos; + + return static_cast( m_nPosition ); +} + +int FileStream::Seek( offset_type lOff, int nFrom ){ + fseek( m_hFile, lOff, nFrom ); + + return ftell( m_hFile ); +} + +MemStream::position_type MemStream::GetPosition() const { + return m_nPosition; +} + +FileStream::position_type FileStream::GetPosition() const { + return ftell( m_hFile ); +} + +void MemStream::GrowFile( size_type nNewLen ){ + if ( nNewLen > m_nBufferSize ) { + // grow the buffer + size_type nNewBufferSize = m_nBufferSize; + + // determine new buffer size + while ( nNewBufferSize < nNewLen ) + nNewBufferSize += m_nGrowBytes; + + // allocate new buffer + unsigned char* lpNew; + if ( m_pBuffer == NULL ) { + lpNew = static_cast( malloc( nNewBufferSize ) ); + } + else{ + lpNew = static_cast( realloc( m_pBuffer, nNewBufferSize ) ); + } + + m_pBuffer = lpNew; + m_nBufferSize = nNewBufferSize; + } +} + +void MemStream::Flush(){ + // Nothing to be done +} + +void FileStream::Flush(){ + if ( m_hFile == NULL ) { + return; + } + + fflush( m_hFile ); +} + +void MemStream::Abort(){ + Close(); +} + +void FileStream::Abort(){ + if ( m_hFile != NULL ) { + // close but ignore errors + if ( m_bCloseOnDelete ) { + fclose( m_hFile ); + } + m_hFile = NULL; + m_bCloseOnDelete = false; + } +} + +void MemStream::SetLength( size_type nNewLen ){ + if ( nNewLen > m_nBufferSize ) { + GrowFile( nNewLen ); + } + + if ( nNewLen < m_nPosition ) { + m_nPosition = nNewLen; + } + + m_nFileSize = nNewLen; +} + +void FileStream::SetLength( size_type nNewLen ){ + fseek( m_hFile, static_cast( nNewLen ), SEEK_SET ); +} + +MemStream::size_type MemStream::GetLength() const { + return m_nFileSize; +} + +FileStream::size_type FileStream::GetLength() const { + size_type nLen, nCur; + + // Seek is a non const operation + nCur = ftell( m_hFile ); + fseek( m_hFile, 0, SEEK_END ); + nLen = ftell( m_hFile ); + fseek( m_hFile, static_cast( nCur ), SEEK_SET ); + + return nLen; +} diff --git a/libs/profile/file.h b/libs/profile/file.h new file mode 100644 index 0000000..c4cdc14 --- /dev/null +++ b/libs/profile/file.h @@ -0,0 +1,166 @@ +/* + 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. + */ + +// +// file.h +//////////////////////////////////////////////////// + +#if !defined( INCLUDED_PROFILE_FILE_H ) +#define INCLUDED_PROFILE_FILE_H + +#include "idatastream.h" + +/*! + API for data streams + + Based on an initial implementation by Loki software + modified to be abstracted and shared across modules + + NOTE: why IDataStream and not IStream? because IStream is defined in windows IDL headers + */ + +class IDataStream : public InputStream, public OutputStream +{ +public: +typedef int offset_type; +typedef std::size_t position_type; + +virtual void IncRef() = 0; ///< Increment the number of references to this object +virtual void DecRef() = 0; ///< Decrement the reference count + +virtual position_type GetPosition() const = 0; +virtual int Seek( offset_type lOff, int nFrom ) = 0; + +virtual void SetLength( size_type nNewLen ) = 0; +virtual size_type GetLength() const = 0; + +virtual char* ReadString( char* pBuf, size_type nMax ) = 0; +virtual int GetChar() = 0; + +virtual int PutChar( int c ) = 0; +virtual void printf( const char*, ... ) = 0; ///< completely matches the usual printf behaviour + +virtual void Abort() = 0; +virtual void Flush() = 0; +virtual void Close() = 0; +}; + +#include + +class MemStream : public IDataStream +{ +public: +MemStream(); +MemStream( size_type nLen ); +virtual ~MemStream(); + +int refCount; +void IncRef() { refCount++; } +void DecRef() { + refCount--; if ( refCount <= 0 ) { + delete this; + } +} + +protected: +// MemFile specific: +size_type m_nGrowBytes; +size_type m_nPosition; +size_type m_nBufferSize; +size_type m_nFileSize; +unsigned char* m_pBuffer; +bool m_bAutoDelete; +void GrowFile( size_type nNewLen ); + +public: +position_type GetPosition() const; +int Seek( offset_type lOff, int nFrom ); +void SetLength( size_type nNewLen ); +size_type GetLength() const; + +unsigned char* GetBuffer() const +{ return m_pBuffer; } + +size_type read( byte_type* buffer, size_type length ); +size_type write( const byte_type* buffer, size_type length ); + +char* ReadString( char* pBuf, size_type nMax ); +int GetChar(); + +int PutChar( int c ); +void printf( const char*, ... ); ///< \todo implement on MemStream + +void Abort(); +void Flush(); +void Close(); +bool Open( const char *filename, const char *mode ); +}; + +class FileStream : public IDataStream +{ +public: +FileStream(); +virtual ~FileStream(); + +int refCount; +void IncRef() { refCount++; } +void DecRef() { + refCount--; if ( refCount <= 0 ) { + delete this; + } +} + +protected: +// DiscFile specific: +FILE* m_hFile; +bool m_bCloseOnDelete; + +public: +position_type GetPosition() const; +int Seek( offset_type lOff, int nFrom ); +void SetLength( size_type nNewLen ); +size_type GetLength() const; + +size_type read( byte_type* buffer, size_type length ); +size_type write( const byte_type* buffer, size_type length ); + +char* ReadString( char* pBuf, size_type nMax ); +int GetChar(); + +int PutChar( int c ); +void printf( const char*, ... ); ///< completely matches the usual printf behaviour + +void Abort(); +void Flush(); +void Close(); +bool Open( const char *filename, const char *mode ); +}; + +#endif diff --git a/libs/profile/profile.cpp b/libs/profile/profile.cpp new file mode 100644 index 0000000..c1505e0 --- /dev/null +++ b/libs/profile/profile.cpp @@ -0,0 +1,292 @@ +/* + 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. + */ + +// +// Application settings load/save +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "profile.h" + +#include +#include +#include + +#include "file.h" + +#include + +#include "str.h" + + +// ============================================================================= +// Static functions + +bool read_var( const char *filename, const char *section, const char *key, char *value ){ + char line[1024], *ptr; + FILE *rc; + + rc = fopen( filename, "rt" ); + + if ( rc == NULL ) { + return false; + } + + while ( fgets( line, 1024, rc ) != 0 ) + { + // First we find the section + if ( line[0] != '[' ) { + continue; + } + + ptr = strchr( line, ']' ); + *ptr = '\0'; + + if ( strcmp( &line[1], section ) == 0 ) { + while ( fgets( line, 1024, rc ) != 0 ) + { + ptr = strchr( line, '=' ); + + if ( ptr == NULL ) { + // reached the end of the section + fclose( rc ); + return false; + } + *ptr = '\0'; + + // remove spaces + while ( line[strlen( line ) - 1] == ' ' ) + line[strlen( line ) - 1] = '\0'; + + if ( strcmp( line, key ) == 0 ) { + strcpy( value, ptr + 1 ); + fclose( rc ); + + if ( value[strlen( value ) - 1] == 10 || value[strlen( value ) - 1] == 13 || value[strlen( value ) - 1] == 32 ) { + value[strlen( value ) - 1] = 0; + } + + return true; + } + } + } + } + + fclose( rc ); + return false; +} + +bool save_var( const char *filename, const char *section, const char *key, const char *value ){ + char line[1024], *ptr; + MemStream old_rc; + bool found; + FILE *rc; + + rc = fopen( filename, "rb" ); + + if ( rc != NULL ) { + unsigned int len; + void *buf; + + fseek( rc, 0, SEEK_END ); + len = ftell( rc ); + rewind( rc ); + buf = malloc( len ); + fread( buf, len, 1, rc ); + old_rc.write( reinterpret_cast( buf ), len ); + free( buf ); + fclose( rc ); + old_rc.Seek( 0, SEEK_SET ); + } + + // TTimo: changed to binary writing. It doesn't seem to affect linux version, and win32 version was happending a lot of '\n' + rc = fopen( filename, "wb" ); + + if ( rc == NULL ) { + return false; + } + + // First we need to find the section + found = false; + while ( old_rc.ReadString( line, 1024 ) != NULL ) + { + fputs( line, rc ); + + if ( line[0] == '[' ) { + ptr = strchr( line, ']' ); + *ptr = '\0'; + + if ( strcmp( &line[1], section ) == 0 ) { + found = true; + break; + } + } + } + + if ( !found ) { + fputs( "\n", rc ); + fprintf( rc, "[%s]\n", section ); + } + + fprintf( rc, "%s=%s\n", key, value ); + + while ( old_rc.ReadString( line, 1024 ) != NULL ) + { + ptr = strchr( line, '=' ); + + if ( ptr != NULL ) { + *ptr = '\0'; + + if ( strcmp( line, key ) == 0 ) { + break; + } + + *ptr = '='; + fputs( line, rc ); + } + else + { + fputs( line, rc ); + break; + } + } + + while ( old_rc.ReadString( line, 1024 ) != NULL ) + fputs( line, rc ); + + fclose( rc ); + return true; +} + +// ============================================================================= +// Global functions + +bool profile_save_int( const char *filename, const char *section, const char *key, int value ){ + char buf[16]; + sprintf( buf, "%d", value ); + return save_var( filename, section, key, buf ); +} + +bool profile_save_float( const char *filename, const char *section, const char *key, float value ){ + char buf[16]; + sprintf( buf, "%f", value ); + return save_var( filename, section, key, buf ); +} + +bool profile_save_string( const char * filename, const char *section, const char *key, const char *value ){ + return save_var( filename, section, key, value ); +} + +bool profile_save_buffer( const char * rc_path, const char *name, void *buffer, unsigned int size ){ + bool ret = false; + char filename[1024]; + sprintf( filename, "%s/%s.bin", rc_path, name ); + FILE *f; + + f = fopen( filename, "wb" ); + + if ( f != NULL ) { + if ( fwrite( buffer, size, 1, f ) == 1 ) { + ret = true; + } + + fclose( f ); + } + + return ret; +} + +bool profile_load_buffer( const char * rc_path, const char *name, void *buffer, unsigned int *plSize ){ + char filename[1024]; + sprintf( filename, "%s/%s.bin", rc_path, name ); + bool ret = false; + unsigned int len; + FILE *f; + + f = fopen( filename, "rb" ); + + if ( f != NULL ) { + fseek( f, 0, SEEK_END ); + len = ftell( f ); + rewind( f ); + + if ( len > *plSize ) { + len = *plSize; + } + else{ + *plSize = len; + } + + if ( fread( buffer, len, 1, f ) == 1 ) { + ret = true; + } + + fclose( f ); + } + + return ret; +} + +int profile_load_int( const char *filename, const char *section, const char *key, int default_value ){ + char value[1024]; + + if ( read_var( filename, section, key, value ) ) { + return atoi( value ); + } + else{ + return default_value; + } +} + +float profile_load_float( const char *filename, const char *section, const char *key, float default_value ){ + char value[1024]; + + if ( read_var( filename, section, key, value ) ) { + return static_cast( atof( value ) ); + } + else{ + return default_value; + } +} + +char* profile_load_string( const char *filename, const char *section, const char *key, const char *default_value ){ + static Str ret; + char value[1024]; + + if ( read_var( filename, section, key, value ) ) { + ret = value; + } + else{ + ret = default_value; + } + + return (char*)ret.GetBuffer(); +} diff --git a/libs/profile/profile.h b/libs/profile/profile.h new file mode 100644 index 0000000..493743f --- /dev/null +++ b/libs/profile/profile.h @@ -0,0 +1,49 @@ +/* + 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_PROFILE_PROFILE_H ) +#define INCLUDED_PROFILE_PROFILE_H + +// profile functions - kind of utility lib +// they are kind of dumb, they expect to get the path to the .ini file or to the prefs directory when called +// load_buffer and save_buffer expect the path only, theyll build a $(pszName).bin file +bool profile_save_int( const char *filename, const char *section, const char *key, int value ); +bool profile_save_float( const char *filename, const char *section, const char *key, float value ); +bool profile_save_string( const char *filename, const char *section, const char *key, const char *value ); +bool profile_save_buffer( const char *rc_path, const char *pszName, void *pvBuf, unsigned int lSize ); +bool profile_load_buffer( const char *rc_path, const char *pszName, void *pvBuf, unsigned int *plSize ); +int profile_load_int( const char *filename, const char *section, const char *key, int default_value ); +float profile_load_float( const char *filename, const char *section, const char *key, float default_value ); +char* profile_load_string( const char *filename, const char *section, const char *key, const char *default_value ); +// used in the command map code +bool read_var( const char *filename, const char *section, const char *key, char *value ); +bool save_var( const char *filename, const char *section, const char *key, const char *value ); + +#endif diff --git a/libs/property.h b/libs/property.h new file mode 100644 index 0000000..abdb32f --- /dev/null +++ b/libs/property.h @@ -0,0 +1,167 @@ +#ifndef INCLUDED_IMPORTEXPORT_H +#define INCLUDED_IMPORTEXPORT_H + +#include "generic/callback.h" +#include "string/string.h" + +template +struct Property { + // todo: just return T, don't use continuation passing style + Callback &returnz)> get; + Callback set; +}; + +// implementation + +template +struct PropertyImpl { + static void Export(const Self &self, const Callback &returnz) { + returnz(self); + } + + static void Import(Self &self, T value) { + self = value; + } +}; + +namespace detail { + + template + using propertyimpl_self = typename std::remove_reference>::type; + + template + using propertyimpl_other = get_argument; + + template + using propertyimpl_other_free = get_argument; + +} + +// adaptor + +template< + class Self, + class T = Self, + class I = PropertyImpl +> +struct PropertyAdaptor { + using Type = Self; + using Other = T; + + using Get = ConstReferenceCaller &), I::Export>; + using Set = ReferenceCaller; +}; + +template< + class T, + class I +> +struct PropertyAdaptorFree { + using Other = T; + + using Get = FreeCaller &), I::Export>; + using Set = FreeCaller; +}; + +// explicit full + +template +Property make_property(typename A::Type &self) { + return {typename A::Get(self), typename A::Set(self)}; +} + +template +Property make_property() { + return {typename A::Get(), typename A::Set()}; +} + +// explicit impl + +template, class T = detail::propertyimpl_other> +using property_impl = PropertyAdaptor; + +template> +Property make_property(Self &self) { + return make_property>(self); +} + +template> +using property_impl_free = PropertyAdaptorFree; + +template> +Property make_property() { + return make_property>(); +} + +// implicit + +template +Property make_property(Self &self) { + return make_property>(self); +} + +// chain + +template +Property> make_property_chain(detail::propertyimpl_self &it) { + using DST = detail::propertyimpl_other; + using SRC = detail::propertyimpl_self; + using X = detail::propertyimpl_self; + + using A = property_impl; + struct I { + static void ExportThunk(const Callback &self, SRC value) { + PropertyImpl::Export(value, self); + } + + static void Export(const X &self, const Callback &returnz) { + A::Get::thunk_(self, ConstReferenceCaller, void(SRC), ExportThunk>(returnz)); + } + + static void Import(X &self, DST value) { + SRC out; + PropertyImpl::Import(out, value); + A::Set::thunk_(self, out); + } + }; + return make_property>(it); +} + +template +Property> make_property_chain() { + using DST = detail::propertyimpl_other; + using SRC = detail::propertyimpl_self; + + using A = property_impl_free; + struct I { + static void ExportThunk(const Callback &self, SRC value) { + PropertyImpl::Export(value, self); + } + + static void Export(const Callback &returnz) { + A::Get::thunk_(nullptr, ConstReferenceCaller, void(SRC), ExportThunk>(returnz)); + } + + static void Import(DST value) { + SRC out; + PropertyImpl::Import(out, value); + A::Set::thunk_(nullptr, out); + } + }; + return make_property>(); +} + +// specializations + +template<> +struct PropertyImpl { + static void Export(const CopiedString &self, const Callback &returnz) { + returnz(self.c_str()); + } + + static void Import(CopiedString &self, const char *value) { + self = value; + } +}; + +#endif diff --git a/libs/render.h b/libs/render.h new file mode 100644 index 0000000..45914a4 --- /dev/null +++ b/libs/render.h @@ -0,0 +1,1209 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_RENDER_H ) +#define INCLUDED_RENDER_H + +/// \file +/// \brief High-level constructs for efficient OpenGL rendering. + +#include "irender.h" +#include "igl.h" + +#include "container/array.h" +#include "math/vector.h" +#include "math/pi.h" + +#include + +typedef unsigned int RenderIndex; +const GLenum RenderIndexTypeID = GL_UNSIGNED_INT; + +/// \brief A resizable buffer of indices. +class IndexBuffer +{ +typedef std::vector Indices; +Indices m_data; +public: +typedef Indices::iterator iterator; +typedef Indices::const_iterator const_iterator; + +iterator begin(){ + return m_data.begin(); +} +const_iterator begin() const { + return m_data.begin(); +} +iterator end(){ + return m_data.end(); +} +const_iterator end() const { + return m_data.end(); +} + +bool empty() const { + return m_data.empty(); +} +std::size_t size() const { + return m_data.size(); +} +const RenderIndex* data() const { + return &( *m_data.begin() ); +} +RenderIndex& operator[]( std::size_t index ){ + return m_data[index]; +} +const RenderIndex& operator[]( std::size_t index ) const { + return m_data[index]; +} +void clear(){ + m_data.clear(); +} +void reserve( std::size_t max_indices ){ + m_data.reserve( max_indices ); +} +void insert( RenderIndex index ){ + m_data.push_back( index ); +} +void swap( IndexBuffer& other ){ + std::swap( m_data, m_data ); +} +}; + +namespace std +{ +/// \brief Swaps the values of \p self and \p other. +/// Overloads std::swap. +inline void swap( IndexBuffer& self, IndexBuffer& other ){ + self.swap( other ); +} +} + +/// \brief A resizable buffer of vertices. +/// \param Vertex The vertex data type. +template +class VertexBuffer +{ +typedef typename std::vector Vertices; +Vertices m_data; +public: +typedef typename Vertices::iterator iterator; +typedef typename Vertices::const_iterator const_iterator; + +iterator begin(){ + return m_data.begin(); +} +iterator end(){ + return m_data.end(); +} +const_iterator begin() const { + return m_data.begin(); +} +const_iterator end() const { + return m_data.end(); +} + +bool empty() const { + return m_data.empty(); +} +RenderIndex size() const { + return RenderIndex( m_data.size() ); +} +const Vertex* data() const { + return &( *m_data.begin() ); +} +Vertex& operator[]( std::size_t index ){ + return m_data[index]; +} +const Vertex& operator[]( std::size_t index ) const { + return m_data[index]; +} + +void clear(){ + m_data.clear(); +} +void reserve( std::size_t max_vertices ){ + m_data.reserve( max_vertices ); +} +void push_back( const Vertex& vertex ){ + m_data.push_back( vertex ); +} +}; + +/// \brief A wrapper around a VertexBuffer which inserts only vertices which have not already been inserted. +/// \param Vertex The vertex data type. Must support operator<, operator== and operator!=. +/// For best performance, quantise vertices before inserting them. +template +class UniqueVertexBuffer +{ +typedef VertexBuffer Vertices; +Vertices& m_data; + +struct bnode +{ + bnode() + : m_left( 0 ), m_right( 0 ){ + } + RenderIndex m_left; + RenderIndex m_right; +}; + +std::vector m_btree; +RenderIndex m_prev0; +RenderIndex m_prev1; +RenderIndex m_prev2; + +RenderIndex find_or_insert( const Vertex& vertex ){ + RenderIndex index = 0; + + while ( 1 ) + { + if ( vertex < m_data[index] ) { + bnode& node = m_btree[index]; + if ( node.m_left != 0 ) { + index = node.m_left; + continue; + } + else + { + node.m_left = RenderIndex( m_btree.size() ); + m_btree.push_back( bnode() ); + m_data.push_back( vertex ); + return RenderIndex( m_btree.size() - 1 ); + } + } + if ( m_data[index] < vertex ) { + bnode& node = m_btree[index]; + if ( node.m_right != 0 ) { + index = node.m_right; + continue; + } + else + { + node.m_right = RenderIndex( m_btree.size() ); + m_btree.push_back( bnode() ); + m_data.push_back( vertex ); + return RenderIndex( m_btree.size() - 1 ); + } + } + + return index; + } +} +public: +UniqueVertexBuffer( Vertices& data ) + : m_data( data ), m_prev0( 0 ), m_prev1( 0 ), m_prev2( 0 ){ +} + +typedef typename Vertices::const_iterator iterator; + +iterator begin() const { + return m_data.begin(); +} +iterator end() const { + return m_data.end(); +} + +std::size_t size() const { + return m_data.size(); +} +const Vertex* data() const { + return &( *m_data.begin() ); +} +Vertex& operator[]( std::size_t index ){ + return m_data[index]; +} +const Vertex& operator[]( std::size_t index ) const { + return m_data[index]; +} + +void clear(){ + m_prev0 = 0; + m_prev1 = 0; + m_prev2 = 0; + m_data.clear(); + m_btree.clear(); +} +void reserve( std::size_t max_vertices ){ + m_data.reserve( max_vertices ); + m_btree.reserve( max_vertices ); +} +/// \brief Returns the index of the element equal to \p vertex. +RenderIndex insert( const Vertex& vertex ){ + if ( m_data.empty() ) { + m_data.push_back( vertex ); + m_btree.push_back( bnode() ); + return 0; + } + + if ( m_data[m_prev0] == vertex ) { + return m_prev0; + } + if ( m_prev1 != m_prev0 && m_data[m_prev1] == vertex ) { + return m_prev1; + } + if ( m_prev2 != m_prev0 && m_prev2 != m_prev1 && m_data[m_prev2] == vertex ) { + return m_prev2; + } + + m_prev2 = m_prev1; + m_prev1 = m_prev0; + m_prev0 = find_or_insert( vertex ); + + return m_prev0; +} +}; + + +/// \brief A 4-byte colour. +struct Colour4b +{ + unsigned char r, g, b, a; + + Colour4b(){ + } + + Colour4b( unsigned char _r, unsigned char _g, unsigned char _b, unsigned char _a ) + : r( _r ), g( _g ), b( _b ), a( _a ){ + } +}; + +const Colour4b colour_vertex( 0, 255, 0, 255 ); +const Colour4b colour_selected( 0, 0, 255, 255 ); + +inline bool operator<( const Colour4b& self, const Colour4b& other ){ + if ( self.r != other.r ) { + return self.r < other.r; + } + if ( self.g != other.g ) { + return self.g < other.g; + } + if ( self.b != other.b ) { + return self.b < other.b; + } + if ( self.a != other.a ) { + return self.a < other.a; + } + return false; +} + +inline bool operator==( const Colour4b& self, const Colour4b& other ){ + return self.r == other.r && self.g == other.g && self.b == other.b && self.a == other.a; +} + +inline bool operator!=( const Colour4b& self, const Colour4b& other ){ + return !operator==( self, other ); +} + +/// \brief A 3-float vertex. +struct Vertex3f : public Vector3 +{ + Vertex3f(){ + } + + Vertex3f( float _x, float _y, float _z ) + : Vector3( _x, _y, _z ){ + } +}; + +inline bool operator<( const Vertex3f& self, const Vertex3f& other ){ + if ( self.x() != other.x() ) { + return self.x() < other.x(); + } + if ( self.y() != other.y() ) { + return self.y() < other.y(); + } + if ( self.z() != other.z() ) { + return self.z() < other.z(); + } + return false; +} + +inline bool operator==( const Vertex3f& self, const Vertex3f& other ){ + return self.x() == other.x() && self.y() == other.y() && self.z() == other.z(); +} + +inline bool operator!=( const Vertex3f& self, const Vertex3f& other ){ + return !operator==( self, other ); +} + + +inline Vertex3f vertex3f_from_array( const float* array ){ + return Vertex3f( array[0], array[1], array[2] ); +} + +inline float* vertex3f_to_array( Vertex3f& vertex ){ + return reinterpret_cast( &vertex ); +} + +inline const float* vertex3f_to_array( const Vertex3f& vertex ){ + return reinterpret_cast( &vertex ); +} + +const Vertex3f vertex3f_identity( 0, 0, 0 ); + +inline Vertex3f vertex3f_for_vector3( const Vector3& vector3 ){ + return Vertex3f( vector3.x(), vector3.y(), vector3.z() ); +} + +inline const Vector3& vertex3f_to_vector3( const Vertex3f& vertex ){ + return vertex; +} + +inline Vector3& vertex3f_to_vector3( Vertex3f& vertex ){ + return vertex; +} + + +/// \brief A 3-float normal. +struct Normal3f : public Vector3 +{ + Normal3f(){ + } + + Normal3f( float _x, float _y, float _z ) + : Vector3( _x, _y, _z ){ + } +}; + +inline bool operator<( const Normal3f& self, const Normal3f& other ){ + if ( self.x() != other.x() ) { + return self.x() < other.x(); + } + if ( self.y() != other.y() ) { + return self.y() < other.y(); + } + if ( self.z() != other.z() ) { + return self.z() < other.z(); + } + return false; +} + +inline bool operator==( const Normal3f& self, const Normal3f& other ){ + return self.x() == other.x() && self.y() == other.y() && self.z() == other.z(); +} + +inline bool operator!=( const Normal3f& self, const Normal3f& other ){ + return !operator==( self, other ); +} + + +inline Normal3f normal3f_from_array( const float* array ){ + return Normal3f( array[0], array[1], array[2] ); +} + +inline float* normal3f_to_array( Normal3f& normal ){ + return reinterpret_cast( &normal ); +} + +inline const float* normal3f_to_array( const Normal3f& normal ){ + return reinterpret_cast( &normal ); +} + +inline Normal3f normal3f_for_vector3( const Vector3& vector3 ){ + return Normal3f( vector3.x(), vector3.y(), vector3.z() ); +} + +inline const Vector3& normal3f_to_vector3( const Normal3f& normal ){ + return normal; +} + +inline Vector3& normal3f_to_vector3( Normal3f& normal ){ + return normal; +} + + +/// \brief A 2-float texture-coordinate set. +struct TexCoord2f : public Vector2 +{ + TexCoord2f(){ + } + + TexCoord2f( float _s, float _t ) + : Vector2( _s, _t ){ + } + + float& s(){ + return x(); + } + const float& s() const { + return x(); + } + float& t(){ + return y(); + } + const float& t() const { + return y(); + } +}; + +inline bool operator<( const TexCoord2f& self, const TexCoord2f& other ){ + if ( self.s() != other.s() ) { + return self.s() < other.s(); + } + if ( self.t() != other.t() ) { + return self.t() < other.t(); + } + return false; +} + +inline bool operator==( const TexCoord2f& self, const TexCoord2f& other ){ + return self.s() == other.s() && self.t() == other.t(); +} + +inline bool operator!=( const TexCoord2f& self, const TexCoord2f& other ){ + return !operator==( self, other ); +} + + +inline float* texcoord2f_to_array( TexCoord2f& texcoord ){ + return reinterpret_cast( &texcoord ); +} + +inline const float* texcoord2f_to_array( const TexCoord2f& texcoord ){ + return reinterpret_cast( &texcoord ); +} + +inline const TexCoord2f& texcoord2f_from_array( const float* array ){ + return *reinterpret_cast( array ); +} + +inline TexCoord2f texcoord2f_for_vector2( const Vector2& vector2 ){ + return TexCoord2f( vector2.x(), vector2.y() ); +} + +inline Vector4 colour4f_for_vector4( const Vector4& vector4 ){ + return vector4; +} + +inline const Vector2& texcoord2f_to_vector2( const TexCoord2f& vertex ){ + return vertex; +} + +inline Vector2& texcoord2f_to_vector2( TexCoord2f& vertex ){ + return vertex; +} +inline Vector4& colour4f_to_vector4( Vector4& vertex ){ + return vertex; +} + +/// \brief Returns \p normal rescaled to be unit-length. +inline Normal3f normal3f_normalised( const Normal3f& normal ){ + return normal3f_for_vector3( vector3_normalised( normal3f_to_vector3( normal ) ) ); +} + +enum UnitSphereOctant +{ + UNITSPHEREOCTANT_000 = 0 << 0 | 0 << 1 | 0 << 2, + UNITSPHEREOCTANT_001 = 0 << 0 | 0 << 1 | 1 << 2, + UNITSPHEREOCTANT_010 = 0 << 0 | 1 << 1 | 0 << 2, + UNITSPHEREOCTANT_011 = 0 << 0 | 1 << 1 | 1 << 2, + UNITSPHEREOCTANT_100 = 1 << 0 | 0 << 1 | 0 << 2, + UNITSPHEREOCTANT_101 = 1 << 0 | 0 << 1 | 1 << 2, + UNITSPHEREOCTANT_110 = 1 << 0 | 1 << 1 | 0 << 2, + UNITSPHEREOCTANT_111 = 1 << 0 | 1 << 1 | 1 << 2, +}; + +/// \brief Returns the octant for \p normal indicating the sign of the region of unit-sphere space it lies within. +inline UnitSphereOctant normal3f_classify_octant( const Normal3f& normal ){ + return static_cast( + ( ( normal.x() > 0 ) << 0 ) | ( ( normal.y() > 0 ) << 1 ) | ( ( normal.z() > 0 ) << 2 ) + ); +} + +/// \brief Returns \p normal with its components signs made positive based on \p octant. +inline Normal3f normal3f_fold_octant( const Normal3f& normal, UnitSphereOctant octant ){ + switch ( octant ) + { + case UNITSPHEREOCTANT_000: + return Normal3f( -normal.x(), -normal.y(), -normal.z() ); + case UNITSPHEREOCTANT_001: + return Normal3f( normal.x(), -normal.y(), -normal.z() ); + case UNITSPHEREOCTANT_010: + return Normal3f( -normal.x(), normal.y(), -normal.z() ); + case UNITSPHEREOCTANT_011: + return Normal3f( normal.x(), normal.y(), -normal.z() ); + case UNITSPHEREOCTANT_100: + return Normal3f( -normal.x(), -normal.y(), normal.z() ); + case UNITSPHEREOCTANT_101: + return Normal3f( normal.x(), -normal.y(), normal.z() ); + case UNITSPHEREOCTANT_110: + return Normal3f( -normal.x(), normal.y(), normal.z() ); + case UNITSPHEREOCTANT_111: + return Normal3f( normal.x(), normal.y(), normal.z() ); + } + return Normal3f(); +} + +/// \brief Reverses the effect of normal3f_fold_octant() on \p normal with \p octant. +/// \p normal must have been obtained with normal3f_fold_octant(). +/// \p octant must have been obtained with normal3f_classify_octant(). +inline Normal3f normal3f_unfold_octant( const Normal3f& normal, UnitSphereOctant octant ){ + return normal3f_fold_octant( normal, octant ); +} + +enum UnitSphereSextant +{ + UNITSPHERESEXTANT_XYZ = 0, + UNITSPHERESEXTANT_XZY = 1, + UNITSPHERESEXTANT_YXZ = 2, + UNITSPHERESEXTANT_YZX = 3, + UNITSPHERESEXTANT_ZXY = 4, + UNITSPHERESEXTANT_ZYX = 5, +}; + +/// \brief Returns the sextant for \p normal indicating how to sort its components so that x > y > z. +/// All components of \p normal must be positive. +/// \p normal must be normalised. +inline UnitSphereSextant normal3f_classify_sextant( const Normal3f& normal ){ + return + normal.x() >= normal.y() + ? normal.x() >= normal.z() + ? normal.y() >= normal.z() + ? UNITSPHERESEXTANT_XYZ + : UNITSPHERESEXTANT_XZY + : UNITSPHERESEXTANT_ZXY + : normal.y() >= normal.z() + ? normal.x() >= normal.z() + ? UNITSPHERESEXTANT_YXZ + : UNITSPHERESEXTANT_YZX + : UNITSPHERESEXTANT_ZYX; +} + +/// \brief Returns \p normal with its components sorted so that x > y > z based on \p sextant. +/// All components of \p normal must be positive. +/// \p normal must be normalised. +inline Normal3f normal3f_fold_sextant( const Normal3f& normal, UnitSphereSextant sextant ){ + switch ( sextant ) + { + case UNITSPHERESEXTANT_XYZ: + return Normal3f( normal.x(), normal.y(), normal.z() ); + case UNITSPHERESEXTANT_XZY: + return Normal3f( normal.x(), normal.z(), normal.y() ); + case UNITSPHERESEXTANT_YXZ: + return Normal3f( normal.y(), normal.x(), normal.z() ); + case UNITSPHERESEXTANT_YZX: + return Normal3f( normal.y(), normal.z(), normal.x() ); + case UNITSPHERESEXTANT_ZXY: + return Normal3f( normal.z(), normal.x(), normal.y() ); + case UNITSPHERESEXTANT_ZYX: + return Normal3f( normal.z(), normal.y(), normal.x() ); + } + return Normal3f(); +} + +/// \brief Reverses the effect of normal3f_fold_sextant() on \p normal with \p sextant. +/// \p normal must have been obtained with normal3f_fold_sextant(). +/// \p sextant must have been obtained with normal3f_classify_sextant(). +inline Normal3f normal3f_unfold_sextant( const Normal3f& normal, UnitSphereSextant sextant ){ + return normal3f_fold_sextant( normal, sextant ); +} + +const std::size_t c_quantise_normal = 1 << 6; + +/// \brief All the components of \p folded must be positive and sorted so that x > y > z. +inline Normal3f normal3f_folded_quantised( const Normal3f& folded ){ + // compress + double scale = static_cast( c_quantise_normal ) / ( folded.x() + folded.y() + folded.z() ); + unsigned int zbits = static_cast( folded.z() * scale ); + unsigned int ybits = static_cast( folded.y() * scale ); + + // decompress + return normal3f_normalised( Normal3f( + static_cast( c_quantise_normal - zbits - ybits ), + static_cast( ybits ), + static_cast( zbits ) + ) ); +} + +/// \brief Returns \p normal quantised by compressing and then decompressing its representation. +inline Normal3f normal3f_quantised_custom( const Normal3f& normal ){ + UnitSphereOctant octant = normal3f_classify_octant( normal ); + Normal3f folded = normal3f_fold_octant( normal, octant ); + UnitSphereSextant sextant = normal3f_classify_sextant( folded ); + folded = normal3f_fold_sextant( folded, sextant ); + return normal3f_unfold_octant( normal3f_unfold_sextant( normal3f_folded_quantised( folded ), sextant ), octant ); +} + + + +struct spherical_t +{ + double longditude, latitude; + + spherical_t( double _longditude, double _latitude ) + : longditude( _longditude ), latitude( _latitude ){ + } +}; + +/* + { + theta = 2pi * U; + phi = acos((2 * V) - 1); + + U = theta / 2pi; + V = (cos(phi) + 1) / 2; + } + + longitude = atan(y / x); + latitude = acos(z); + */ +struct uniformspherical_t +{ + double U, V; + + uniformspherical_t( double U_, double V_ ) + : U( U_ ), V( V_ ){ + } +}; + + +inline spherical_t spherical_from_normal3f( const Normal3f& normal ){ + return spherical_t( normal.x() == 0 ? c_pi / 2 : normal.x() > 0 ? atan( normal.y() / normal.x() ) : atan( normal.y() / normal.x() ) + c_pi, acos( normal.z() ) ); +} + +inline Normal3f normal3f_from_spherical( const spherical_t& spherical ){ + return Normal3f( + static_cast( cos( spherical.longditude ) * sin( spherical.latitude ) ), + static_cast( sin( spherical.longditude ) * sin( spherical.latitude ) ), + static_cast( cos( spherical.latitude ) ) + ); +} + +inline uniformspherical_t uniformspherical_from_spherical( const spherical_t& spherical ){ + return uniformspherical_t( spherical.longditude * c_inv_2pi, ( cos( spherical.latitude ) + 1 ) * 0.5 ); +} + +inline spherical_t spherical_from_uniformspherical( const uniformspherical_t& uniformspherical ){ + return spherical_t( c_2pi * uniformspherical.U, acos( ( 2 * uniformspherical.V ) - 1 ) ); +} + +inline uniformspherical_t uniformspherical_from_normal3f( const Normal3f& normal ){ + return uniformspherical_from_spherical( spherical_from_normal3f( normal ) ); + //return uniformspherical_t(atan2(normal.y / normal.x) * c_inv_2pi, (normal.z + 1) * 0.5); +} + +inline Normal3f normal3f_from_uniformspherical( const uniformspherical_t& uniformspherical ){ + return normal3f_from_spherical( spherical_from_uniformspherical( uniformspherical ) ); +} + +/// \brief Returns a single-precision \p component quantised to \p precision. +inline float float_quantise( float component, float precision ){ + return float_snapped( component, precision ); +} + +/// \brief Returns a double-precision \p component quantised to \p precision. +inline double double_quantise( double component, double precision ){ + return float_snapped( component, precision ); +} + +inline spherical_t spherical_quantised( const spherical_t& spherical, float snap ){ + return spherical_t( double_quantise( spherical.longditude, snap ), double_quantise( spherical.latitude, snap ) ); +} + +inline uniformspherical_t uniformspherical_quantised( const uniformspherical_t& uniformspherical, float snap ){ + return uniformspherical_t( double_quantise( uniformspherical.U, snap ), double_quantise( uniformspherical.V, snap ) ); +} + +/// \brief Returns a \p vertex quantised to \p precision. +inline Vertex3f vertex3f_quantised( const Vertex3f& vertex, float precision ){ + return Vertex3f( float_quantise( vertex.x(), precision ), float_quantise( vertex.y(), precision ), float_quantise( vertex.z(), precision ) ); +} + +/// \brief Returns a \p normal quantised to a fixed precision. +inline Normal3f normal3f_quantised( const Normal3f& normal ){ + return normal3f_quantised_custom( normal ); + //return normal3f_from_spherical(spherical_quantised(spherical_from_normal3f(normal), snap)); + //return normal3f_from_uniformspherical(uniformspherical_quantised(uniformspherical_from_normal3f(normal), snap)); + // float_quantise(normal.x, snap), float_quantise(normal.y, snap), float_quantise(normal.y, snap)); +} + +/// \brief Returns a \p texcoord quantised to \p precision. +inline TexCoord2f texcoord2f_quantised( const TexCoord2f& texcoord, float precision ){ + return TexCoord2f( float_quantise( texcoord.s(), precision ), float_quantise( texcoord.t(), precision ) ); +} + +/// \brief Standard vertex type for lines and points. +struct PointVertex +{ + Colour4b colour; + Vertex3f vertex; + + PointVertex(){ + } + PointVertex( Vertex3f _vertex ) + : colour( Colour4b( 255, 255, 255, 255 ) ), vertex( _vertex ){ + } + PointVertex( Vertex3f _vertex, Colour4b _colour ) + : colour( _colour ), vertex( _vertex ){ + } +}; + +inline bool operator<( const PointVertex& self, const PointVertex& other ){ + if ( self.vertex != other.vertex ) { + return self.vertex < other.vertex; + } + if ( self.colour != other.colour ) { + return self.colour < other.colour; + } + return false; +} + +inline bool operator==( const PointVertex& self, const PointVertex& other ){ + return self.colour == other.colour && self.vertex == other.vertex; +} + +inline bool operator!=( const PointVertex& self, const PointVertex& other ){ + return !operator==( self, other ); +} + +/// \brief Standard vertex type for lit/textured meshes. +struct ArbitraryMeshVertex +{ + TexCoord2f texcoord; + Normal3f normal; + Vertex3f vertex; + Normal3f tangent; + Normal3f bitangent; + Vector4 colour; + + ArbitraryMeshVertex() : tangent( 0, 0, 0 ), bitangent( 0, 0, 0 ), colour( 1, 1, 1, 1 ){ + } + ArbitraryMeshVertex( Vertex3f _vertex, Normal3f _normal, TexCoord2f _texcoord ) + : texcoord( _texcoord ), normal( _normal ), vertex( _vertex ), tangent( 0, 0, 0 ), bitangent( 0, 0, 0 ), colour( 1, 1, 1, 1 ){ + } +}; + +inline bool operator<( const ArbitraryMeshVertex& self, const ArbitraryMeshVertex& other ){ + if ( self.texcoord != other.texcoord ) { + return self.texcoord < other.texcoord; + } + if ( self.normal != other.normal ) { + return self.normal < other.normal; + } + if ( self.vertex != other.vertex ) { + return self.vertex < other.vertex; + } + return false; +} + +inline bool operator==( const ArbitraryMeshVertex& self, const ArbitraryMeshVertex& other ){ + return self.texcoord == other.texcoord && self.normal == other.normal && self.vertex == other.vertex; +} + +inline bool operator!=( const ArbitraryMeshVertex& self, const ArbitraryMeshVertex& other ){ + return !operator==( self, other ); +} + +const float c_quantise_vertex = 1.f / static_cast( 1 << 3 ); + +/// \brief Returns \p v with vertex quantised to a fixed precision. +inline PointVertex pointvertex_quantised( const PointVertex& v ){ + return PointVertex( vertex3f_quantised( v.vertex, c_quantise_vertex ), v.colour ); +} + +const float c_quantise_texcoord = 1.f / static_cast( 1 << 8 ); + +/// \brief Returns \p v with vertex, normal and texcoord quantised to a fixed precision. +inline ArbitraryMeshVertex arbitrarymeshvertex_quantised( const ArbitraryMeshVertex& v ){ + return ArbitraryMeshVertex( vertex3f_quantised( v.vertex, c_quantise_vertex ), normal3f_quantised( v.normal ), texcoord2f_quantised( v.texcoord, c_quantise_texcoord ) ); +} + + +/// \brief Sets up the OpenGL colour and vertex arrays for \p array. +inline void pointvertex_gl_array( const PointVertex* array ){ + glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &array->colour ); + glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &array->vertex ); +} + +class RenderablePointArray : public OpenGLRenderable +{ +const Array& m_array; +const GLenum m_mode; +public: +RenderablePointArray( const Array& array, GLenum mode ) + : m_array( array ), m_mode( mode ){ +} +void render( RenderStateFlags state ) const { +#define NV_DRIVER_BUG 1 +#if NV_DRIVER_BUG + glColorPointer( 4, GL_UNSIGNED_BYTE, 0, 0 ); + glVertexPointer( 3, GL_FLOAT, 0, 0 ); + glDrawArrays( GL_TRIANGLE_FAN, 0, 0 ); +#endif + pointvertex_gl_array( m_array.data() ); + glDrawArrays( m_mode, 0, GLsizei( m_array.size() ) ); +} +}; + +class RenderablePointVector : public OpenGLRenderable +{ +std::vector m_vector; +const GLenum m_mode; +public: +RenderablePointVector( GLenum mode ) + : m_mode( mode ){ +} + +void render( RenderStateFlags state ) const { + pointvertex_gl_array( &m_vector.front() ); + glDrawArrays( m_mode, 0, GLsizei( m_vector.size() ) ); +} + +std::size_t size() const { + return m_vector.size(); +} +bool empty() const { + return m_vector.empty(); +} +void clear(){ + m_vector.clear(); +} +void reserve( std::size_t size ){ + m_vector.reserve( size ); +} +void push_back( const PointVertex& point ){ + m_vector.push_back( point ); +} +}; + + +class RenderableVertexBuffer : public OpenGLRenderable +{ +const GLenum m_mode; +const VertexBuffer& m_vertices; +public: +RenderableVertexBuffer( GLenum mode, const VertexBuffer& vertices ) + : m_mode( mode ), m_vertices( vertices ){ +} + +void render( RenderStateFlags state ) const { + pointvertex_gl_array( m_vertices.data() ); + glDrawArrays( m_mode, 0, m_vertices.size() ); +} +}; + +class RenderableIndexBuffer : public OpenGLRenderable +{ +const GLenum m_mode; +const IndexBuffer& m_indices; +const VertexBuffer& m_vertices; +public: +RenderableIndexBuffer( GLenum mode, const IndexBuffer& indices, const VertexBuffer& vertices ) + : m_mode( mode ), m_indices( indices ), m_vertices( vertices ){ +} + +void render( RenderStateFlags state ) const { +#if 1 + pointvertex_gl_array( m_vertices.data() ); + glDrawElements( m_mode, GLsizei( m_indices.size() ), RenderIndexTypeID, m_indices.data() ); +#else + glBegin( m_mode ); + if ( state & RENDER_COLOURARRAY != 0 ) { + for ( std::size_t i = 0; i < m_indices.size(); ++i ) + { + glColor4ubv( &m_vertices[m_indices[i]].colour.r ); + glVertex3fv( &m_vertices[m_indices[i]].vertex.x ); + } + } + else + { + for ( std::size_t i = 0; i < m_indices.size(); ++i ) + { + glVertex3fv( &m_vertices[m_indices[i]].vertex.x ); + } + } + glEnd(); +#endif +} +}; + + +class RemapXYZ +{ +public: +static void set( Vertex3f& vertex, float x, float y, float z ){ + vertex.x() = x; + vertex.y() = y; + vertex.z() = z; +} +}; + +class RemapYZX +{ +public: +static void set( Vertex3f& vertex, float x, float y, float z ){ + vertex.x() = z; + vertex.y() = x; + vertex.z() = y; +} +}; + +class RemapZXY +{ +public: +static void set( Vertex3f& vertex, float x, float y, float z ){ + vertex.x() = y; + vertex.y() = z; + vertex.z() = x; +} +}; + +template +inline void draw_circle( 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; + while ( count < segments ) + { + PointVertex* i = vertices + count; + PointVertex* j = vertices + ( ( segments << 1 ) - ( count + 1 ) ); + + PointVertex* k = i + ( segments << 1 ); + PointVertex* l = j + ( segments << 1 ); + + PointVertex* m = i + ( segments << 2 ); + PointVertex* n = j + ( segments << 2 ); + PointVertex* o = k + ( segments << 2 ); + PointVertex* p = l + ( segments << 2 ); + + remap_policy::set( i->vertex, x,-y, 0 ); + remap_policy::set( k->vertex,-y,-x, 0 ); + remap_policy::set( m->vertex,-x, y, 0 ); + remap_policy::set( o->vertex, y, x, 0 ); + + ++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 ); + remap_policy::set( n->vertex,-y, x, 0 ); + remap_policy::set( p->vertex, x, y, 0 ); + } +} + +#if 0 +class PointVertexArrayIterator +{ +PointVertex* m_point; +public: +PointVertexArrayIterator( PointVertex* point ) + : m_point( point ){ +} +PointVertexArrayIterator& operator++(){ + ++m_point; + return *this; +} +PointVertexArrayIterator operator++( int ){ + PointVertexArrayIterator tmp( *this ); + ++m_point; + return tmp; +} +Vertex3f& operator*(){ + return m_point.vertex; +} +Vertex3f* operator->(){ + return &( operator*() ); +} +} + +template 0.000001f ) { + s.x() = -cross.y() / cross.x(); + } + + if ( fabs( cross.x() ) > 0.000001f ) { + t.x() = -cross.z() / cross.x(); + } + } + + { + Vector3 cross( + vector3_cross( + vector3_subtracted( + Vector3( b.vertex.y(), b.texcoord.s(), b.texcoord.t() ), + Vector3( a.vertex.y(), a.texcoord.s(), a.texcoord.t() ) + ), + vector3_subtracted( + Vector3( c.vertex.y(), c.texcoord.s(), c.texcoord.t() ), + Vector3( a.vertex.y(), a.texcoord.s(), a.texcoord.t() ) + ) + ) + ); + + if ( fabs( cross.x() ) > 0.000001f ) { + s.y() = -cross.y() / cross.x(); + } + + if ( fabs( cross.x() ) > 0.000001f ) { + t.y() = -cross.z() / cross.x(); + } + } + + { + Vector3 cross( + vector3_cross( + vector3_subtracted( + Vector3( b.vertex.z(), b.texcoord.s(), b.texcoord.t() ), + Vector3( a.vertex.z(), a.texcoord.s(), a.texcoord.t() ) + ), + vector3_subtracted( + Vector3( c.vertex.z(), c.texcoord.s(), c.texcoord.t() ), + Vector3( a.vertex.z(), a.texcoord.s(), a.texcoord.t() ) + ) + ) + ); + + if ( fabs( cross.x() ) > 0.000001f ) { + s.z() = -cross.y() / cross.x(); + } + + if ( fabs( cross.x() ) > 0.000001f ) { + t.z() = -cross.z() / cross.x(); + } + } +} + +inline void ArbitraryMeshTriangle_sumTangents( ArbitraryMeshVertex& a, ArbitraryMeshVertex& b, ArbitraryMeshVertex& c ){ + Vector3 s, t; + + ArbitraryMeshTriangle_calcTangents( a, b, c, s, t ); + + reinterpret_cast( a.tangent ) += s; + reinterpret_cast( b.tangent ) += s; + reinterpret_cast( c.tangent ) += s; + + reinterpret_cast( a.bitangent ) += t; + reinterpret_cast( b.bitangent ) += t; + reinterpret_cast( c.bitangent ) += t; +} + + +#endif diff --git a/libs/scenelib.h b/libs/scenelib.h new file mode 100644 index 0000000..e1c05d4 --- /dev/null +++ b/libs/scenelib.h @@ -0,0 +1,965 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_SCENELIB_H ) +#define INCLUDED_SCENELIB_H + +#include "globaldefs.h" +#include "iscenegraph.h" +#include "iselection.h" + +#include "warnings.h" +#include +#include + +#include "math/aabb.h" +#include "transformlib.h" +#include "generic/callback.h" +#include "generic/reference.h" +#include "container/stack.h" +#include "typesystem.h" + +class Selector; +class SelectionTest; +class VolumeTest; +template class BasicVector3; +typedef BasicVector3 Vector3; +template class BasicVector4; +typedef BasicVector4 Vector4; +class Matrix4; +typedef Vector4 Quaternion; +class AABB; + +class ComponentSelectionTestable +{ +public: +STRING_CONSTANT( Name, "ComponentSelectionTestable" ); + +virtual bool isSelectedComponents() const = 0; +virtual void setSelectedComponents( bool select, SelectionSystem::EComponentMode mode ) = 0; +virtual void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ) = 0; +}; + +class ComponentEditable +{ +public: +STRING_CONSTANT( Name, "ComponentEditable" ); + +virtual const AABB& getSelectedComponentsBounds() const = 0; +}; + +class ComponentSnappable +{ +public: +STRING_CONSTANT( Name, "ComponentSnappable" ); + +virtual void snapComponents( float snap ) = 0; +}; + +class Bounded +{ +public: +STRING_CONSTANT( Name, "Bounded" ); + +virtual const AABB& localAABB() const = 0; +}; + +class BrushDoom3 +{ +public: +STRING_CONSTANT( Name, "BrushDoom3" ); + +virtual void setDoom3GroupOrigin( const Vector3& origin ) = 0; +}; + + + + +typedef TypeCastTable NodeTypeCastTable; + +template +class NodeType : public StaticTypeSystemInitialiser +{ +TypeId m_typeId; +public: +typedef typename Type::Name Name; +NodeType() : m_typeId( NODETYPEID_NONE ){ + StaticTypeSystemInitialiser::instance().addInitialiser( InitialiseCaller( *this ) ); +} +void initialise(){ + m_typeId = GlobalSceneGraph().getNodeTypeId( Name() ); +} +typedef MemberCaller, void(), &NodeType::initialise> InitialiseCaller; +TypeId getTypeId(){ +#if GDEF_DEBUG + ASSERT_MESSAGE( m_typeId != NODETYPEID_NONE, "node-type " << makeQuoted( Name() ) << " used before being initialised" ); +#endif + return m_typeId; +} +}; + +template +class StaticNodeType +{ +public: +enum unnamed0 { SIZE = NODETYPEID_MAX }; +static TypeId getTypeId(){ + return Static< NodeType >::instance().getTypeId(); +} +}; + +template +class NodeStaticCast : + public CastInstaller< + StaticNodeType, + StaticCast + > +{ +}; + +template +class NodeContainedCast : + public CastInstaller< + StaticNodeType, + ContainedCast + > +{ +}; + +template +class NodeIdentityCast : + public CastInstaller< + StaticNodeType, + IdentityCast + > +{ +}; + +namespace scene +{ +class Node +{ +public: +enum unnamed0 { eVisible = 0 }; +enum unnamed1 { eHidden = 1 << 0 }; +enum unnamed2 { eFiltered = 1 << 1 }; +enum unnamed3 { eExcluded = 1 << 2 }; + +class Symbiot +{ +public: +virtual void release() = 0; +virtual ~Symbiot(){ +} +}; + +private: +unsigned int m_state; +std::size_t m_refcount; +Symbiot* m_symbiot; +void* m_node; +NodeTypeCastTable& m_casts; + +public: +bool m_isRoot; + +bool isRoot(){ + return m_isRoot; +} + +Node( Symbiot* symbiot, void* node, NodeTypeCastTable& casts ) : + m_state( eVisible ), + m_refcount( 0 ), + m_symbiot( symbiot ), + m_node( node ), + m_casts( casts ), + m_isRoot( false ){ +} +~Node(){ +} + +void IncRef(){ + ASSERT_MESSAGE( m_refcount < ( 1 << 24 ), "Node::decref: uninitialised refcount" ); + ++m_refcount; +} +void DecRef(){ + ASSERT_MESSAGE( m_refcount < ( 1 << 24 ), "Node::decref: uninitialised refcount" ); + if ( --m_refcount == 0 ) { + m_symbiot->release(); + } +} +std::size_t getReferenceCount() const { + return m_refcount; +} + +void* cast( TypeId typeId ) const { + return m_casts.cast( typeId, m_node ); +} + +void enable( unsigned int state ){ + m_state |= state; +} +void disable( unsigned int state ){ + m_state &= ~state; +} +bool visible(){ + return m_state == eVisible; +} +bool excluded(){ + return ( m_state & eExcluded ) != 0; +} +bool operator<( const scene::Node& other ){ + return this < &other; +} +bool operator==( const scene::Node& other ){ + return this == &other; +} +bool operator!=( const scene::Node& other ){ + return this != &other; +} +}; + + +class NullNode : public Node::Symbiot +{ +NodeTypeCastTable m_casts; +Node m_node; +public: +NullNode() : m_node( this, 0, m_casts ){ +} +void release(){ + delete this; +} +scene::Node& node(){ + return m_node; +} +}; +} + +template +class NodeTypeCast +{ +public: +static Type* cast( scene::Node& node ){ + return static_cast( node.cast( StaticNodeType::getTypeId() ) ); +} +static const Type* cast( const scene::Node& node ){ + return static_cast( node.cast( StaticNodeType::getTypeId() ) ); +} +}; + + +inline scene::Instantiable* Node_getInstantiable( scene::Node& node ){ + return NodeTypeCast::cast( node ); +} + +inline scene::Traversable* Node_getTraversable( scene::Node& node ){ + return NodeTypeCast::cast( node ); +} + +inline void Node_traverseSubgraph( scene::Node& node, const scene::Traversable::Walker& walker ){ + if ( walker.pre( node ) ) { + scene::Traversable* traversable = Node_getTraversable( node ); + if ( traversable != 0 ) { + traversable->traverse( walker ); + } + } + walker.post( node ); +} + +inline TransformNode* Node_getTransformNode( scene::Node& node ){ + return NodeTypeCast::cast( node ); +} + + +inline scene::Node& NewNullNode(){ + return ( new scene::NullNode )->node(); +} + +inline void Path_deleteTop( const scene::Path& path ){ + Node_getTraversable( path.parent() )->erase( path.top() ); +} + + + + + +class delete_all : public scene::Traversable::Walker +{ +scene::Node& m_parent; +public: +delete_all( scene::Node& parent ) : m_parent( parent ){ +} +bool pre( scene::Node& node ) const { + return false; +} +void post( scene::Node& node ) const { + Node_getTraversable( m_parent )->erase( node ); +} +}; + +inline void DeleteSubgraph( scene::Node& subgraph ){ + Node_getTraversable( subgraph )->traverse( delete_all( subgraph ) ); +} + + +class EntityUndefined +{ +public: +STRING_CONSTANT( Name, "Entity" ); +}; + +inline bool Node_isEntity( scene::Node& node ){ + return NodeTypeCast::cast( node ) != 0; +} + +template +class EntityWalker : public scene::Graph::Walker +{ +const Functor& functor; +public: +EntityWalker( const Functor& functor ) : functor( functor ){ +} +bool pre( const scene::Path& path, scene::Instance& instance ) const { + if ( Node_isEntity( path.top() ) ) { + functor( instance ); + return false; + } + return true; +} +}; + +template +inline const Functor& Scene_forEachEntity( const Functor& functor ){ + GlobalSceneGraph().traverse( EntityWalker( functor ) ); + return functor; +} + +class BrushUndefined +{ +public: +STRING_CONSTANT( Name, "Brush" ); +}; + +inline bool Node_isBrush( scene::Node& node ){ + return NodeTypeCast::cast( node ) != 0; +} + +class PatchUndefined +{ +public: +STRING_CONSTANT( Name, "Patch" ); +}; + +inline bool Node_isPatch( scene::Node& node ){ + return NodeTypeCast::cast( node ) != 0; +} + +inline bool Node_isPrimitive( scene::Node& node ){ +#if 1 + return Node_isBrush( node ) || Node_isPatch( node ); +#else + return !node.isRoot(); +#endif +} + +class ParentBrushes : public scene::Traversable::Walker +{ +scene::Node& m_parent; +public: +ParentBrushes( scene::Node& parent ) + : m_parent( parent ){ +} +bool pre( scene::Node& node ) const { + return false; +} +void post( scene::Node& node ) const { + if ( Node_isPrimitive( node ) ) { + Node_getTraversable( m_parent )->insert( node ); + } +} +}; + +inline void parentBrushes( scene::Node& subgraph, scene::Node& parent ){ + Node_getTraversable( subgraph )->traverse( ParentBrushes( parent ) ); +} + +class HasBrushes : public scene::Traversable::Walker +{ +bool& m_hasBrushes; +public: +HasBrushes( bool& hasBrushes ) + : m_hasBrushes( hasBrushes ){ + m_hasBrushes = true; +} +bool pre( scene::Node& node ) const { + if ( !Node_isPrimitive( node ) ) { + m_hasBrushes = false; + } + return false; +} +}; + +inline bool node_is_group( scene::Node& node ){ + scene::Traversable* traversable = Node_getTraversable( node ); + if ( traversable != 0 ) { + bool hasBrushes = false; + traversable->traverse( HasBrushes( hasBrushes ) ); + return hasBrushes; + } + return false; +} + +typedef TypeCastTable InstanceTypeCastTable; + +template +class InstanceType : public StaticTypeSystemInitialiser +{ +TypeId m_typeId; +public: +typedef typename Type::Name Name; +InstanceType() : m_typeId( INSTANCETYPEID_NONE ){ + StaticTypeSystemInitialiser::instance().addInitialiser( InitialiseCaller( *this ) ); +} +void initialise(){ + m_typeId = GlobalSceneGraph().getInstanceTypeId( Name() ); +} +typedef MemberCaller, void(), &InstanceType::initialise> InitialiseCaller; +TypeId getTypeId(){ +#if GDEF_DEBUG + ASSERT_MESSAGE( m_typeId != INSTANCETYPEID_NONE, "instance-type " << makeQuoted( Name() ) << " used before being initialised" ); +#endif + return m_typeId; +} +}; + +template +class StaticInstanceType +{ +public: +enum unnamed0 { SIZE = INSTANCETYPEID_MAX }; +static TypeId getTypeId(){ + return Static< InstanceType >::instance().getTypeId(); +} +}; + +template +class InstanceStaticCast : + public CastInstaller< + StaticInstanceType, + StaticCast + > +{ +}; + +template +class InstanceContainedCast : + public CastInstaller< + StaticInstanceType, + ContainedCast + > +{ +}; + +template +class InstanceIdentityCast : + public CastInstaller< + StaticInstanceType, + IdentityCast + > +{ +}; + + +inline Selectable* Instance_getSelectable( scene::Instance& instance ); +inline const Selectable* Instance_getSelectable( const scene::Instance& instance ); + +inline Bounded* Instance_getBounded( scene::Instance& instance ); +inline const Bounded* Instance_getBounded( const scene::Instance& instance ); + +namespace scene +{ +class Instance +{ +class AABBAccumulateWalker : public scene::Graph::Walker +{ +AABB& m_aabb; +mutable std::size_t m_depth; +public: +AABBAccumulateWalker( AABB& aabb ) : m_aabb( aabb ), m_depth( 0 ){ +} +bool pre( const scene::Path& path, scene::Instance& instance ) const { + if ( m_depth == 1 ) { + aabb_extend_by_aabb_safe( m_aabb, instance.worldAABB() ); + } + return ++m_depth != 2; +} +void post( const scene::Path& path, scene::Instance& instance ) const { + --m_depth; +} +}; + + +class TransformChangedWalker : public scene::Graph::Walker +{ +public: +bool pre( const scene::Path& path, scene::Instance& instance ) const { + instance.transformChangedLocal(); + return true; +} +}; + +class ParentSelectedChangedWalker : public scene::Graph::Walker +{ +public: +bool pre( const scene::Path& path, scene::Instance& instance ) const { + instance.parentSelectedChanged(); + return true; +} +}; + +class ChildSelectedWalker : public scene::Graph::Walker +{ +bool& m_childSelected; +mutable std::size_t m_depth; +public: +ChildSelectedWalker( bool& childSelected ) : m_childSelected( childSelected ), m_depth( 0 ){ + m_childSelected = false; +} +bool pre( const scene::Path& path, scene::Instance& instance ) const { + if ( m_depth == 1 && !m_childSelected ) { + m_childSelected = instance.isSelected() || instance.childSelected(); + } + return ++m_depth != 2; +} +void post( const scene::Path& path, scene::Instance& instance ) const { + --m_depth; +} +}; + +Path m_path; +Instance* m_parent; +void* m_instance; +InstanceTypeCastTable& m_casts; + +mutable Matrix4 m_local2world; +mutable AABB m_bounds; +mutable AABB m_childBounds; +mutable bool m_transformChanged; +mutable bool m_transformMutex; +mutable bool m_boundsChanged; +mutable bool m_boundsMutex; +mutable bool m_childBoundsChanged; +mutable bool m_childBoundsMutex; +mutable bool m_isSelected; +mutable bool m_isSelectedChanged; +mutable bool m_childSelected; +mutable bool m_childSelectedChanged; +mutable bool m_parentSelected; +mutable bool m_parentSelectedChanged; +Callback m_childSelectedChangedCallback; +Callback m_transformChangedCallback; + + +void evaluateTransform() const { + if ( m_transformChanged ) { + ASSERT_MESSAGE( !m_transformMutex, "re-entering transform evaluation" ); + m_transformMutex = true; + + m_local2world = ( m_parent != 0 ) ? m_parent->localToWorld() : g_matrix4_identity; + TransformNode* transformNode = Node_getTransformNode( m_path.top() ); + if ( transformNode != 0 ) { + matrix4_multiply_by_matrix4( m_local2world, transformNode->localToParent() ); + } + + m_transformMutex = false; + m_transformChanged = false; + } +} +void evaluateChildBounds() const { + if ( m_childBoundsChanged ) { + ASSERT_MESSAGE( !m_childBoundsMutex, "re-entering bounds evaluation" ); + m_childBoundsMutex = true; + + m_childBounds = AABB(); + + GlobalSceneGraph().traverse_subgraph( AABBAccumulateWalker( m_childBounds ), m_path ); + + m_childBoundsMutex = false; + m_childBoundsChanged = false; + } +} +void evaluateBounds() const { + if ( m_boundsChanged ) { + ASSERT_MESSAGE( !m_boundsMutex, "re-entering bounds evaluation" ); + m_boundsMutex = true; + + m_bounds = childBounds(); + + const Bounded* bounded = Instance_getBounded( *this ); + if ( bounded != 0 ) { + aabb_extend_by_aabb_safe( + m_bounds, + aabb_for_oriented_aabb_safe( bounded->localAABB(), localToWorld() ) + ); + } + + m_boundsMutex = false; + m_boundsChanged = false; + } +} + +Instance( const scene::Instance& other ); +Instance& operator=( const scene::Instance& other ); +public: + +Instance( const scene::Path& path, Instance* parent, void* instance, InstanceTypeCastTable& casts ) : + m_path( path ), + m_parent( parent ), + m_instance( instance ), + m_casts( casts ), + m_local2world( g_matrix4_identity ), + m_transformChanged( true ), + m_transformMutex( false ), + m_boundsChanged( true ), + m_boundsMutex( false ), + m_childBoundsChanged( true ), + m_childBoundsMutex( false ), + m_isSelectedChanged( true ), + m_childSelectedChanged( true ), + m_parentSelectedChanged( true ){ + ASSERT_MESSAGE( ( parent == 0 ) == ( path.size() == 1 ), "instance has invalid parent" ); +} +virtual ~Instance(){ +} + +const scene::Path& path() const { + return m_path; +} + +void* cast( TypeId typeId ) const { + return m_casts.cast( typeId, m_instance ); +} + +const Matrix4& localToWorld() const { + evaluateTransform(); + return m_local2world; +} +void transformChangedLocal(){ + ASSERT_NOTNULL( m_parent ); + m_transformChanged = true; + m_boundsChanged = true; + m_childBoundsChanged = true; + m_transformChangedCallback(); +} +void transformChanged(){ + GlobalSceneGraph().traverse_subgraph( TransformChangedWalker(), m_path ); + boundsChanged(); +} +void setTransformChangedCallback( const Callback& callback ){ + m_transformChangedCallback = callback; +} + + +const AABB& worldAABB() const { + evaluateBounds(); + return m_bounds; +} +const AABB& childBounds() const { + evaluateChildBounds(); + return m_childBounds; +} +void boundsChanged(){ + m_boundsChanged = true; + m_childBoundsChanged = true; + if ( m_parent != 0 ) { + m_parent->boundsChanged(); + } + GlobalSceneGraph().boundsChanged(); +} + +void childSelectedChanged(){ + m_childSelectedChanged = true; + m_childSelectedChangedCallback(); + if ( m_parent != 0 ) { + m_parent->childSelectedChanged(); + } +} +bool childSelected() const { + if ( m_childSelectedChanged ) { + m_childSelectedChanged = false; + GlobalSceneGraph().traverse_subgraph( ChildSelectedWalker( m_childSelected ), m_path ); + } + return m_childSelected; +} + +void setChildSelectedChangedCallback( const Callback& callback ){ + m_childSelectedChangedCallback = callback; +} +void selectedChanged(){ + m_isSelectedChanged = true; + if ( m_parent != 0 ) { + m_parent->childSelectedChanged(); + } + GlobalSceneGraph().traverse_subgraph( ParentSelectedChangedWalker(), m_path ); +} +bool isSelected() const { + if ( m_isSelectedChanged ) { + m_isSelectedChanged = false; + const Selectable* selectable = Instance_getSelectable( *this ); + m_isSelected = selectable != 0 && selectable->isSelected(); + } + return m_isSelected; +} + +void parentSelectedChanged(){ + m_parentSelectedChanged = true; +} +bool parentSelected() const { + if ( m_parentSelectedChanged ) { + m_parentSelectedChanged = false; + m_parentSelected = m_parent != 0 && ( m_parent->isSelected() || m_parent->parentSelected() ); + } + return m_parentSelected; +} +}; +} + +template +class InstanceTypeCast +{ +public: +static Type* cast( scene::Instance& instance ){ + return static_cast( instance.cast( StaticInstanceType::getTypeId() ) ); +} +static const Type* cast( const scene::Instance& instance ){ + return static_cast( instance.cast( StaticInstanceType::getTypeId() ) ); +} +}; + +template +class InstanceWalker : public scene::Graph::Walker +{ +const Functor& m_functor; +public: +InstanceWalker( const Functor& functor ) : m_functor( functor ){ +} +bool pre( const scene::Path& path, scene::Instance& instance ) const { + m_functor( instance ); + return true; +} +}; + +template +class ChildInstanceWalker : public scene::Graph::Walker +{ +const Functor& m_functor; +mutable std::size_t m_depth; +public: +ChildInstanceWalker( const Functor& functor ) : m_functor( functor ), m_depth( 0 ){ +} +bool pre( const scene::Path& path, scene::Instance& instance ) const { + if ( m_depth == 1 ) { + m_functor( instance ); + } + return ++m_depth != 2; +} +void post( const scene::Path& path, scene::Instance& instance ) const { + --m_depth; +} +}; + +template +class InstanceApply : public Functor +{ +public: +InstanceApply( const Functor& functor ) : Functor( functor ){ +} +void operator()( scene::Instance& instance ) const { + Type* result = InstanceTypeCast::cast( instance ); + if ( result != 0 ) { + Functor::operator()( *result ); + } +} +}; + +inline Selectable* Instance_getSelectable( scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} +inline const Selectable* Instance_getSelectable( const scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} + +template +inline void Scene_forEachChildSelectable( const Functor& functor, const scene::Path& path ){ + GlobalSceneGraph().traverse_subgraph( ChildInstanceWalker< InstanceApply >( functor ), path ); +} + +class SelectableSetSelected +{ +bool m_selected; +public: +SelectableSetSelected( bool selected ) : m_selected( selected ){ +} +void operator()( Selectable& selectable ) const { + selectable.setSelected( m_selected ); +} +}; + +inline Bounded* Instance_getBounded( scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} +inline const Bounded* Instance_getBounded( const scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} + +inline Transformable* Instance_getTransformable( scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} +inline const Transformable* Instance_getTransformable( const scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} + + +inline ComponentSelectionTestable* Instance_getComponentSelectionTestable( scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} + +inline ComponentEditable* Instance_getComponentEditable( scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} + +inline ComponentSnappable* Instance_getComponentSnappable( scene::Instance& instance ){ + return InstanceTypeCast::cast( instance ); +} + + +inline void Instance_setSelected( scene::Instance& instance, bool selected ){ + Selectable* selectable = Instance_getSelectable( instance ); + if ( selectable != 0 ) { + selectable->setSelected( selected ); + } +} + +inline bool Instance_isSelected( scene::Instance& instance ){ + Selectable* selectable = Instance_getSelectable( instance ); + if ( selectable != 0 ) { + return selectable->isSelected(); + } + return false; +} + +inline scene::Instance& findInstance( const scene::Path& path ){ + scene::Instance* instance = GlobalSceneGraph().find( path ); + ASSERT_MESSAGE( instance != 0, "findInstance: path not found in scene-graph" ); + return *instance; +} + +inline void selectPath( const scene::Path& path, bool selected ){ + Instance_setSelected( findInstance( path ), selected ); +} + +class SelectChildren : public scene::Traversable::Walker +{ +mutable scene::Path m_path; +public: +SelectChildren( const scene::Path& root ) + : m_path( root ){ +} +bool pre( scene::Node& node ) const { + m_path.push( makeReference( node ) ); + selectPath( m_path, true ); + return false; +} +void post( scene::Node& node ) const { + m_path.pop(); +} +}; + +inline void Entity_setSelected( scene::Instance& entity, bool selected ){ + scene::Node& node = entity.path().top(); + if ( node_is_group( node ) ) { + Node_getTraversable( node )->traverse( SelectChildren( entity.path() ) ); + } + else + { + Instance_setSelected( entity, selected ); + } +} + +inline bool Entity_isSelected( scene::Instance& entity ){ + if ( node_is_group( entity.path().top() ) ) { + return entity.childSelected(); + } + return Instance_isSelected( entity ); +} + + + +class InstanceCounter +{ +public: +unsigned int m_count; +InstanceCounter() : m_count( 0 ){ +} +}; + + +class Counter +{ +public: +virtual void increment() = 0; +virtual void decrement() = 0; +}; + +#include "generic/callback.h" + +class SimpleCounter : public Counter +{ +Callback m_countChanged; +std::size_t m_count; +public: +void setCountChangedCallback( const Callback& countChanged ){ + m_countChanged = countChanged; +} +void increment(){ + ++m_count; + m_countChanged(); +} +void decrement(){ + --m_count; + m_countChanged(); +} +std::size_t get() const { + return m_count; +} +}; + + +template +class ConstReference; +typedef ConstReference PathConstReference; + +#include "generic/referencecounted.h" +typedef SmartReference > NodeSmartReference; + + +#endif diff --git a/libs/script/CMakeLists.txt b/libs/script/CMakeLists.txt new file mode 100644 index 0000000..6e49527 --- /dev/null +++ b/libs/script/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(script + _.cpp + scripttokeniser.h + scripttokenwriter.h + ) diff --git a/libs/script/_.cpp b/libs/script/_.cpp new file mode 100644 index 0000000..e69de29 diff --git a/libs/script/scripttokeniser.h b/libs/script/scripttokeniser.h new file mode 100644 index 0000000..8744aec --- /dev/null +++ b/libs/script/scripttokeniser.h @@ -0,0 +1,343 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_SCRIPT_SCRIPTTOKENISER_H ) +#define INCLUDED_SCRIPT_SCRIPTTOKENISER_H + +#include "iscriplib.h" + +class ScriptTokeniser : public Tokeniser +{ +enum CharType +{ + eWhitespace, + eCharToken, + eNewline, + eCharQuote, + eCharSolidus, + eCharStar, + eCharSpecial, +}; + +typedef bool ( ScriptTokeniser::*Tokenise )( char c ); + +Tokenise m_stack[3]; +Tokenise* m_state; +SingleCharacterInputStream m_istream; +std::size_t m_scriptline; +std::size_t m_scriptcolumn; + +char m_token[MAXTOKEN]; +char* m_write; + +char m_current; +bool m_eof; +bool m_crossline; +bool m_unget; +bool m_emit; + +bool m_special; + +CharType charType( const char c ){ + switch ( c ) + { + case '\n': return eNewline; + case '"': return eCharQuote; + case '/': return eCharSolidus; + case '*': return eCharStar; + case '{': case '(': case '}': case ')': case '[': case ']': case ',': case ':': return ( m_special ) ? eCharSpecial : eCharToken; + } + + if ( c > 32 ) { + return eCharToken; + } + return eWhitespace; +} + +Tokenise state(){ + return *m_state; +} +void push( Tokenise state ){ + ASSERT_MESSAGE( m_state != m_stack + 2, "token parser: illegal stack push" ); + *( ++m_state ) = state; +} +void pop(){ + ASSERT_MESSAGE( m_state != m_stack, "token parser: illegal stack pop" ); + --m_state; +} +void add( const char c ){ + if ( m_write < m_token + MAXTOKEN - 1 ) { + *m_write++ = c; + } +} +void remove(){ + ASSERT_MESSAGE( m_write > m_token, "no char to remove" ); + --m_write; +} + +bool tokeniseDefault( char c ){ + switch ( charType( c ) ) + { + case eNewline: + if ( !m_crossline ) { + globalErrorStream() << Unsigned( getLine() ) << ":" << Unsigned( getColumn() ) << ": unexpected end-of-line before token\n"; + return false; + } + break; + case eCharToken: + case eCharStar: + push( Tokenise( &ScriptTokeniser::tokeniseToken ) ); + add( c ); + break; + case eCharSpecial: + push( Tokenise( &ScriptTokeniser::tokeniseSpecial ) ); + add( c ); + break; + case eCharQuote: + push( Tokenise( &ScriptTokeniser::tokeniseQuotedToken ) ); + break; + case eCharSolidus: + push( Tokenise( &ScriptTokeniser::tokeniseSolidus ) ); + break; + default: + break; + } + return true; +} +bool tokeniseToken( char c ){ + switch ( charType( c ) ) + { + case eNewline: + case eWhitespace: + case eCharQuote: + case eCharSpecial: + pop(); + m_emit = true; // emit token + break; + case eCharSolidus: +#if 0 //SPoG: ignore comments in the middle of tokens. + push( Tokenise( &ScriptTokeniser::tokeniseSolidus ) ); + break; +#endif + case eCharToken: + case eCharStar: + add( c ); + break; + default: + break; + } + return true; +} +bool tokeniseQuotedToken( char c ){ + switch ( charType( c ) ) + { + case eNewline: + if ( m_crossline ) { + globalErrorStream() << Unsigned( getLine() ) << ":" << Unsigned( getColumn() ) << ": unexpected end-of-line in quoted token\n"; + return false; + } + break; + case eWhitespace: + case eCharToken: + case eCharSolidus: + case eCharStar: + case eCharSpecial: + add( c ); + break; + case eCharQuote: + pop(); + push( Tokenise( &ScriptTokeniser::tokeniseEndQuote ) ); + break; + default: + break; + } + return true; +} +bool tokeniseSolidus( char c ){ + switch ( charType( c ) ) + { + case eNewline: + case eWhitespace: + case eCharQuote: + case eCharSpecial: + pop(); + add( '/' ); + m_emit = true; // emit single slash + break; + case eCharToken: + pop(); + add( '/' ); + add( c ); + break; + case eCharSolidus: + pop(); + push( Tokenise( &ScriptTokeniser::tokeniseComment ) ); + break; // dont emit single slash + case eCharStar: + pop(); + push( Tokenise( &ScriptTokeniser::tokeniseBlockComment ) ); + break; // dont emit single slash + default: + break; + } + return true; +} +bool tokeniseComment( char c ){ + if ( c == '\n' ) { + pop(); + if ( state() == Tokenise( &ScriptTokeniser::tokeniseToken ) ) { + pop(); + m_emit = true; // emit token immediatly preceding comment + } + } + return true; +} +bool tokeniseBlockComment( char c ){ + if ( c == '*' ) { + pop(); + push( Tokenise( &ScriptTokeniser::tokeniseEndBlockComment ) ); + } + return true; +} +bool tokeniseEndBlockComment( char c ){ + switch ( c ) + { + case '/': + pop(); + if ( state() == Tokenise( &ScriptTokeniser::tokeniseToken ) ) { + pop(); + m_emit = true; // emit token immediatly preceding comment + } + break; // dont emit comment + case '*': + break; // no state change + default: + pop(); + push( Tokenise( &ScriptTokeniser::tokeniseBlockComment ) ); + break; + } + return true; +} +bool tokeniseEndQuote( char c ){ + pop(); + m_emit = true; // emit quoted token + return true; +} +bool tokeniseSpecial( char c ){ + pop(); + m_emit = true; // emit single-character token + return true; +} + +/// Returns true if a token was successfully parsed. +bool tokenise(){ + m_write = m_token; + while ( !eof() ) + { + char c = m_current; + + if ( !( ( *this ).*state() )( c ) ) { + // parse error + m_eof = true; + return false; + } + if ( m_emit ) { + m_emit = false; + return true; + } + + if ( c == '\n' ) { + ++m_scriptline; + m_scriptcolumn = 1; + } + else + { + ++m_scriptcolumn; + } + + m_eof = !m_istream.readChar( m_current ); + } + return m_write != m_token; +} + +const char* fillToken(){ + if ( !tokenise() ) { + return 0; + } + + add( '\0' ); + return m_token; +} + +bool eof(){ + return m_eof; +} + +public: +ScriptTokeniser( TextInputStream& istream, bool special ) + : m_state( m_stack ), + m_istream( istream ), + m_scriptline( 1 ), + m_scriptcolumn( 1 ), + m_crossline( false ), + m_unget( false ), + m_emit( false ), + m_special( special ){ + m_stack[0] = Tokenise( &ScriptTokeniser::tokeniseDefault ); + m_eof = !m_istream.readChar( m_current ); + m_token[MAXTOKEN - 1] = '\0'; +} +void release(){ + delete this; +} +void nextLine(){ + m_crossline = true; +} +const char* getToken(){ + if ( m_unget ) { + m_unget = false; + return m_token; + } + + return fillToken(); +} +void ungetToken(){ + ASSERT_MESSAGE( !m_unget, "can't unget more than one token" ); + m_unget = true; +} +std::size_t getLine() const { + return m_scriptline; +} +std::size_t getColumn() const { + return m_scriptcolumn; +} +}; + + +inline Tokeniser& NewScriptTokeniser( TextInputStream& istream ){ + return *( new ScriptTokeniser( istream, true ) ); +} + +inline Tokeniser& NewSimpleTokeniser( TextInputStream& istream ){ + return *( new ScriptTokeniser( istream, false ) ); +} + +#endif diff --git a/libs/script/scripttokenwriter.h b/libs/script/scripttokenwriter.h new file mode 100644 index 0000000..49ee57a --- /dev/null +++ b/libs/script/scripttokenwriter.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_SCRIPT_SCRIPTTOKENWRITER_H ) +#define INCLUDED_SCRIPT_SCRIPTTOKENWRITER_H + +#include "iscriplib.h" + +class SimpleTokenWriter : public TokenWriter +{ +public: +SimpleTokenWriter( TextOutputStream& ostream ) + : m_ostream( ostream ), m_separator( '\n' ){ +} +~SimpleTokenWriter(){ + writeSeparator(); +} +void release(){ + delete this; +} +void nextLine(){ + m_separator = '\n'; +} +void writeToken( const char* token ){ + ASSERT_MESSAGE( strchr( token, ' ' ) == 0, "token contains whitespace: " ); + writeSeparator(); + m_ostream << token; +} +void writeString( const char* string ){ + writeSeparator(); + m_ostream << '"' << string << '"'; +} +void writeInteger( int i ){ + writeSeparator(); + m_ostream << i; +} +void writeUnsigned( std::size_t i ){ + writeSeparator(); + m_ostream << Unsigned( i ); +} +void writeFloat( double f ){ + writeSeparator(); + m_ostream << Decimal( f ); +} + +private: +void writeSeparator(){ + m_ostream << m_separator; + m_separator = ' '; +} +TextOutputStream& m_ostream; +char m_separator; +}; + +inline TokenWriter& NewSimpleTokenWriter( TextOutputStream& ostream ){ + return *( new SimpleTokenWriter( ostream ) ); +} + +#endif diff --git a/libs/selectionlib.h b/libs/selectionlib.h new file mode 100644 index 0000000..06a905c --- /dev/null +++ b/libs/selectionlib.h @@ -0,0 +1,179 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_SELECTIONLIB_H ) +#define INCLUDED_SELECTIONLIB_H + +#include "iselection.h" +#include "generic/callback.h" +#include "scenelib.h" +#include + +class SelectableBool : public Selectable +{ +bool m_selected; +public: +SelectableBool() + : m_selected( false ) +{} + +void setSelected( bool select = true ){ + m_selected = select; +} +bool isSelected() const { + return m_selected; +} +}; + +class ObservedSelectable : public Selectable +{ +SelectionChangeCallback m_onchanged; +bool m_selected; +public: +ObservedSelectable( const SelectionChangeCallback& onchanged ) : m_onchanged( onchanged ), m_selected( false ){ +} +ObservedSelectable( const ObservedSelectable& other ) : Selectable( other ), m_onchanged( other.m_onchanged ), m_selected( false ){ + setSelected( other.isSelected() ); +} +ObservedSelectable& operator=( const ObservedSelectable& other ){ + setSelected( other.isSelected() ); + return *this; +} +~ObservedSelectable(){ + setSelected( false ); +} + +void setSelected( bool select ){ + if ( select ^ m_selected ) { + m_selected = select; + + m_onchanged( *this ); + } +} +bool isSelected() const { + return m_selected; +} +}; + +class SelectableInstance : public scene::Instance +{ +class TypeCasts +{ +InstanceTypeCastTable m_casts; +public: +TypeCasts(){ + InstanceContainedCast::install( m_casts ); +} +InstanceTypeCastTable& get(){ + return m_casts; +} +}; + +ObservedSelectable m_selectable; +public: + +typedef LazyStatic StaticTypeCasts; + +SelectableInstance( const scene::Path& path, scene::Instance* parent, void* instance = 0, InstanceTypeCastTable& casts = StaticTypeCasts::instance().get() ) : + Instance( path, parent, instance != 0 ? instance : this, casts ), + m_selectable( SelectedChangedCaller( *this ) ){ +} + +Selectable& get( NullType){ + return m_selectable; +} + +Selectable& getSelectable(){ + return m_selectable; +} +const Selectable& getSelectable() const { + return m_selectable; +} + +void selectedChanged( const Selectable& selectable ){ + GlobalSelectionSystem().getObserver ( SelectionSystem::ePrimitive )( selectable ); + GlobalSelectionSystem().onSelectedChanged( *this, selectable ); + + Instance::selectedChanged(); +} +typedef MemberCaller SelectedChangedCaller; +}; + + +template +inline bool range_check( Iterator start, Iterator finish, Iterator iter ){ + for (; start != finish; ++start ) + { + if ( start == iter ) { + return true; + } + } + return false; +} + +#include + +template +class SelectionList +{ +typedef std::list List; +List m_selection; +public: +typedef typename List::iterator iterator; +typedef typename List::const_iterator const_iterator; + +iterator begin(){ + return m_selection.begin(); +} +const_iterator begin() const { + return m_selection.begin(); +} +iterator end(){ + return m_selection.end(); +} +const_iterator end() const { + return m_selection.end(); +} +bool empty() const { + return m_selection.empty(); +} +std::size_t size() const { + return m_selection.size(); +} +Selected& back(){ + return *m_selection.back(); +} +Selected& back() const { + return *m_selection.back(); +} +void append( Selected& selected ){ + m_selection.push_back( &selected ); +} +void erase( Selected& selected ){ + typename List::reverse_iterator i = std::find( m_selection.rbegin(), m_selection.rend(), &selected ); + ASSERT_MESSAGE( i != m_selection.rend(), "selection-tracking error" ); + ASSERT_MESSAGE( range_check( m_selection.begin(), m_selection.end(), --i.base() ), "selection-tracking error" ); + m_selection.erase( --i.base() ); +} +}; + + +#endif diff --git a/libs/shaderlib.h b/libs/shaderlib.h new file mode 100644 index 0000000..ee97243 --- /dev/null +++ b/libs/shaderlib.h @@ -0,0 +1,84 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_SHADERLIB_H ) +#define INCLUDED_SHADERLIB_H + +#include "string/string.h" +#include "character.h" +#include "ishaders.h" + +inline bool shader_equal( const char* shader, const char* other ){ + return string_equal_nocase( shader, other ); +} + +inline bool shader_equal_n( const char* shader, const char* other, std::size_t n ){ + return string_equal_nocase_n( shader, other, n ); +} + +inline bool shader_less( const char* shader, const char* other ){ + return string_less_nocase( shader, other ); +} + +inline bool shader_equal_prefix( const char* string, const char* prefix ){ + return shader_equal_n( string, prefix, string_length( prefix ) ); +} + +class shader_less_t +{ +public: +bool operator()( const CopiedString& shader, const CopiedString& other ) const { + return shader_less( shader.c_str(), other.c_str() ); +} +}; + +inline bool shader_valid( const char* shader ){ + return string_is_ascii( shader ) + && strchr( shader, ' ' ) == 0 + && strchr( shader, '\n' ) == 0 + && strchr( shader, '\r' ) == 0 + && strchr( shader, '\t' ) == 0 + && strchr( shader, '\v' ) == 0 + && strchr( shader, '\\' ) == 0; +} + +inline const char* GlobalTexturePrefix_get(){ + return GlobalShaderSystem().getTexturePrefix(); +} + +inline bool shader_is_texture( const char* name ){ + return shader_equal_prefix( name, GlobalTexturePrefix_get() ); +} + +inline const char* shader_get_textureName( const char* name ){ + return name + string_length( GlobalTexturePrefix_get() ); +} + +inline bool texdef_name_valid( const char* name ){ + return shader_valid( name ) && shader_is_texture( name ); +} + +inline const char* texdef_name_default(){ + return GlobalTexturePrefix_get(); +} + + +#endif diff --git a/libs/signal/CMakeLists.txt b/libs/signal/CMakeLists.txt new file mode 100644 index 0000000..499a483 --- /dev/null +++ b/libs/signal/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(signal + isignal.h + signal.cpp signal.h + signalfwd.h + ) diff --git a/libs/signal/isignal.h b/libs/signal/isignal.h new file mode 100644 index 0000000..a8dcdfc --- /dev/null +++ b/libs/signal/isignal.h @@ -0,0 +1,166 @@ + +#if !defined( INCLUDED_ISIGNAL_H ) +#define INCLUDED_ISIGNAL_H + +#include "generic/callback.h" +#include "signal/signalfwd.h" + +class SignalHandlerResult +{ +bool value; +public: +explicit SignalHandlerResult( bool value ) : value( value ){ +} +bool operator==( SignalHandlerResult other ) const { + return value == other.value; +} +bool operator!=( SignalHandlerResult other ) const { + return !operator==( other ); +} +}; + +const SignalHandlerResult SIGNAL_CONTINUE_EMISSION = SignalHandlerResult( false ); +const SignalHandlerResult SIGNAL_STOP_EMISSION = SignalHandlerResult( true ); + +template +class SignalHandlerCallerN; + +template +class SignalHandlerCallerN { +public: + using func = SignalHandlerResult(Ts...); + + static SignalHandlerResult call(Ts... args) { + Caller::call(args...); + return SIGNAL_CONTINUE_EMISSION; + } +}; + +template +using SignalHandlerCaller = SignalHandlerCallerN>; + +template +using SignalHandlerCaller1 = SignalHandlerCaller; + +template +using SignalHandlerCaller2 = SignalHandlerCaller; + +template +using SignalHandlerCaller3 = SignalHandlerCaller; + +template +using SignalHandlerCaller4 = SignalHandlerCaller; + +template +class TypeEqual { +public: + using type = False; +}; + +template +class TypeEqual { +public: + using type = True; +}; + +template class Wrapper> +class SignalHandlerN : public CB { +public: + template + SignalHandlerN(const BindFirstOpaque &caller) + : CB(BindFirstOpaque, + get_result_type + >::type>(caller.getBound())) { + } +}; + +class SignalHandler : public SignalHandlerN, SignalHandlerCaller1> { + using SignalHandlerN, SignalHandlerCaller1>::SignalHandlerN; +}; + +template +inline SignalHandler makeSignalHandler( const BindFirstOpaque& caller ){ + return SignalHandler( caller ); +} +template +inline SignalHandler makeSignalHandler(const Caller &caller, get_argument callee) { + return SignalHandler( BindFirstOpaque( callee ) ); +} + +template +class SignalHandler1 : public SignalHandlerN, SignalHandlerCaller2> { + using SignalHandlerN, SignalHandlerCaller2>::SignalHandlerN; +}; + +template +inline SignalHandler1> makeSignalHandler1(const BindFirstOpaque &caller) { + return SignalHandler1>(caller); +} +template +inline SignalHandler1> +makeSignalHandler1(const Caller &caller, get_argument callee) { + return SignalHandler1>(BindFirstOpaque(callee)); +} + +template +class SignalHandler2 + : public SignalHandlerN, SignalHandlerCaller3> { + using SignalHandlerN, SignalHandlerCaller3>::SignalHandlerN; +}; + +template +inline SignalHandler2< + get_argument, + get_argument +> makeSignalHandler2(const BindFirstOpaque &caller) { + return SignalHandler2< + get_argument, + get_argument + >( caller ); +} +template +inline SignalHandler2< + get_argument, + get_argument +> makeSignalHandler2(const Caller &caller, get_argument callee) { + return SignalHandler2< + get_argument, + get_argument + >(BindFirstOpaque(callee)); +} + +template +class SignalHandler3 + : public SignalHandlerN, SignalHandlerCaller4> { + using SignalHandlerN, SignalHandlerCaller4>::SignalHandlerN; +}; + +template +inline SignalHandler3< + get_argument, + get_argument, + get_argument +> makeSignalHandler3(const BindFirstOpaque &caller) { + return SignalHandler3< + get_argument, + get_argument, + get_argument + >( caller ); +} +template +inline SignalHandler3< + get_argument, + get_argument, + get_argument +> makeSignalHandler3(const Caller &caller, get_argument callee) { + return SignalHandler3< + get_argument, + get_argument, + get_argument + >(BindFirstOpaque(callee)); +} + +#endif diff --git a/libs/signal/signal.cpp b/libs/signal/signal.cpp new file mode 100644 index 0000000..caade86 --- /dev/null +++ b/libs/signal/signal.cpp @@ -0,0 +1,96 @@ + +#include "signal.h" + + + +namespace +{ +class Test +{ +}; +class A1 +{ +}; +class A2 +{ +}; +class A3 +{ +}; + +SignalHandlerResult handler0( Test& ){ + return SIGNAL_CONTINUE_EMISSION; +} +typedef Function TestHandler0; + +int function0( Test& ){ + return 7; +} +typedef Function TestFunction0; + +SignalHandlerResult handler1( Test&, A1 ){ + return SIGNAL_CONTINUE_EMISSION; +} +typedef Function TestHandler1; + +void function1( Test&, A1 ){ +} +typedef ReferenceCaller TestFunction1; + +SignalHandlerResult handler2( Test&, A1, A2 ){ + return SIGNAL_CONTINUE_EMISSION; +} +typedef Function TestHandler2; + +void function2( Test&, A1, A2 ){ +} +typedef Function TestFunction2; + +SignalHandlerResult handler3( Test&, A1, A2, A3 ){ + return SIGNAL_CONTINUE_EMISSION; +} +typedef Function TestHandler3; + +void function3( Test&, A1, A2, A3 ){ +} +typedef Function TestFunction3; + +void testSignals(){ + Test test; + { + Signal0 e0; + Signal0::handler_id_type a = e0.connectLast( makeSignalHandler( TestHandler0(), test ) ); // signal handler from direct caller returning result + Signal0::handler_id_type b = e0.connectFirst( makeSignalHandler( TestFunction0(), test ) ); // signal handler from direct caller returning int + e0(); + e0.disconnect( a ); + e0.disconnect( b ); + } + { + typedef Signal1 Signal1Test; + Signal1Test e1; + Signal1Test::handler_id_type a = e1.connectLast( makeSignalHandler1( TestHandler1(), test ) ); // signal handler from direct caller with one argument, returning result + Signal1Test::handler_id_type b = e1.connectFirst( makeSignalHandler1( TestFunction1( test ) ) ); // signal handler from opaque caller with one argument, returning void + e1( A1() ); + e1.disconnect( a ); + e1.disconnect( b ); + } + { + typedef Signal2 Signal2Test; + Signal2Test e2; + Signal2Test::handler_id_type a = e2.connectLast( makeSignalHandler2( TestHandler2(), test ) ); // signal handler from direct caller with two arguments, returning result + Signal2Test::handler_id_type b = e2.connectLast( makeSignalHandler2( TestFunction2(), test ) ); // signal handler from direct caller with two arguments, returning void + e2( A1(), A2() ); + e2.disconnect( a ); + e2.disconnect( b ); + } + { + typedef Signal3 Signal3Test; + Signal3Test e3; + Signal3Test::handler_id_type a = e3.connectLast( makeSignalHandler3( TestHandler3(), test ) ); // signal handler from direct caller with three arguments, returning result + Signal3Test::handler_id_type b = e3.connectLast( makeSignalHandler3( TestFunction3(), test ) ); // signal handler from direct caller with three arguments, returning void + e3( A1(), A2(), A3() ); + e3.disconnect( a ); + e3.disconnect( b ); + } +} +} diff --git a/libs/signal/signal.h b/libs/signal/signal.h new file mode 100644 index 0000000..3d4c039 --- /dev/null +++ b/libs/signal/signal.h @@ -0,0 +1,342 @@ + +#if !defined( INCLUDED_SIGNAL_H ) +#define INCLUDED_SIGNAL_H + +#include "isignal.h" +#include "memory/allocator.h" +#include "debugging/debugging.h" +#include + +namespace ListDetail +{ +struct ListNodeBase +{ + ListNodeBase* next; + ListNodeBase* prev; +}; + +inline void list_initialise( ListNodeBase& self ){ + self.next = self.prev = &self; +} + +inline void list_swap( ListNodeBase& self, ListNodeBase& other ){ + ListNodeBase 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( ListNodeBase* node, ListNodeBase* next ){ + node->next = next; + node->prev = next->prev; + next->prev = node; + node->prev->next = node; +} +inline void node_unlink( ListNodeBase* node ){ + node->prev->next = node->next; + node->next->prev = node->prev; +} + +template +struct ListNode : public ListNodeBase +{ + Value value; + + ListNode( const Value& value ) : value( value ){ + } + ListNode* getNext() const { + return static_cast( next ); + } + ListNode* getPrev() const { + return static_cast( prev ); + } +}; + +template +class NonConstTraits +{ +public: +typedef Type value_type; +typedef value_type* pointer; +typedef value_type& reference; + +template +struct rebind +{ + typedef NonConstTraits other; +}; +}; + +template +class ConstTraits +{ +public: +typedef Type value_type; +typedef const value_type* pointer; +typedef const value_type& reference; + +template +struct rebind +{ + typedef ConstTraits other; +}; +}; + +template +class ListIterator +{ +public: +typedef std::bidirectional_iterator_tag iterator_category; +typedef std::ptrdiff_t difference_type; +typedef difference_type distance_type; +typedef typename Traits::value_type value_type; +typedef typename Traits::pointer pointer; +typedef typename Traits::reference reference; + +private: +typedef ListNode Node; +typedef typename Traits::template rebind::other NodeTraits; +typedef typename NodeTraits::pointer NodePointer; +typedef typename Traits::template rebind< Opaque >::other OpaqueTraits; +typedef typename OpaqueTraits::pointer OpaquePointer; +NodePointer m_node; + +void increment(){ + m_node = m_node->getNext(); +} +void decrement(){ + m_node = m_node->getPrev(); +} + + +public: +explicit ListIterator( NodePointer node ) : m_node( node ){ +} +explicit ListIterator( OpaquePointer p ) : m_node( reinterpret_cast( p ) ){ +} + +NodePointer node(){ + return m_node; +} +OpaquePointer opaque() const { + return reinterpret_cast( m_node ); +} + +bool operator==( const ListIterator& other ) const { + return m_node == other.m_node; +} +bool operator!=( const ListIterator& other ) const { + return !operator==( other ); +} +ListIterator& operator++(){ + increment(); + return *this; +} +ListIterator operator++( int ){ + ListIterator tmp = *this; + increment(); + return tmp; +} +ListIterator& operator--(){ + decrement(); + return *this; +} +ListIterator operator--( int ){ + ListIterator tmp = *this; + decrement(); + return tmp; +} +reference operator*() const { + return m_node->value; +} +pointer operator->() const { + return &( operator*() ); +} +}; +} + +template > +class List : private Allocator +{ +typedef ListDetail::ListNode Node; +ListDetail::ListNodeBase list; +typedef typename Allocator::template rebind::other NodeAllocator; + +Node* newNode( const Value& value ){ + return new ( NodeAllocator( *this ).allocate( 1 ) )Node( value ); +} +void deleteNode( Node* node ){ + node->~Node(); + NodeAllocator( *this ).deallocate( node, 1 ); +} +public: +typedef Value value_type; +typedef ListDetail::ListIterator< ListDetail::NonConstTraits > iterator; +typedef ListDetail::ListIterator< ListDetail::ConstTraits > const_iterator; + +List(){ + list_initialise( list ); +} +explicit List( const Allocator& allocator ) : Allocator( allocator ){ + list_initialise( list ); +} +~List(){ + for (; list.next != &list; ) + { + Node* node = static_cast( list.next ); + list.next = list.next->next; + deleteNode( node ); + } +} +iterator begin(){ + return iterator( static_cast( list.next ) ); +} +iterator end(){ + return iterator( static_cast( &list ) ); +} +const_iterator begin() const { + return const_iterator( static_cast( list.next ) ); +} +const_iterator end() const { + return const_iterator( static_cast( &list ) ); +} +void push_back( const Value& value ){ + insert( end(), value ); +} +void pop_back( const Value& value ){ + erase( --end(), value ); +} +void push_front( const Value& value ){ + insert( begin(), value ); +} +void pop_front( const Value& value ){ + erase( begin(), value ); +} +iterator insert( iterator pos, const Value& value ){ + Node* node = newNode( value ); + node_link( node, pos.node() ); + return iterator( node ); +} +iterator erase( iterator pos ){ + Node* node = pos.node(); + Node* next = node->getNext(); + node_unlink( node ); + deleteNode( node ); + return iterator( next ); +} +}; + +template +class SignalBase +{ +typedef List SignalList; +SignalList events; + +public: + +typedef Functor handler_type; +typedef Handle< Opaque > handler_id_type; +typedef typename SignalList::iterator iterator; +typedef typename SignalList::const_iterator const_iterator; +iterator begin(){ + return events.begin(); +} +iterator end(){ + return events.end(); +} +const_iterator begin() const { + return events.begin(); +} +const_iterator end() const { + return events.end(); +} +handler_id_type connectFirst( const Functor& event ){ + events.push_front( event ); + return handler_id_type( begin().opaque() ); +} +handler_id_type connectLast( const Functor& event ){ + events.push_back( event ); + return handler_id_type( ( --end() ).opaque() ); +} +bool isConnected( handler_id_type id ){ + for ( iterator i = begin(); i != end(); ++i ) + { + if ( id.get() == i.opaque() ) { + return true; + } + } + return false; +} +handler_id_type connectBefore( handler_id_type id, const Functor& event ){ + ASSERT_MESSAGE( isConnected( id ), "SignalBase::connectBefore: invalid id" ); + return events.insert( iterator( id.get() ), event ).opaque(); +} +handler_id_type connectAfter( handler_id_type id, const Functor& event ){ + ASSERT_MESSAGE( isConnected( id ), "SignalBase::connectAfter: invalid id" ); + return events.insert( ++iterator( id.get() ), event ).opaque(); +} +void disconnect( handler_id_type id ){ + ASSERT_MESSAGE( isConnected( id ), "SignalBase::disconnect: invalid id" ); + events.erase( iterator( id.get() ) ); +} +}; + +///\brief +// It is safe to disconnect the signal handler currently being invoked. +template +inline void invokeSignalHandlers( InputIterator first, InputIterator last, SignalHandlerInvoke invoke ){ + while ( first != last && invoke( *first++ ) != SIGNAL_STOP_EMISSION ) ; +} + +class Signal0 : public SignalBase +{ +public: +void operator()() const { + invokeSignalHandlers( begin(), end(), FunctorInvoke() ); +} +}; + +template +class Signal1 : public SignalBase< SignalHandler1 > +{ +typedef SignalBase< SignalHandler1 > Base; +public: +void operator()( FirstArgument a1 ) const { + invokeSignalHandlers( Base::begin(), Base::end(), FunctorInvoke( a1 ) ); +} +}; + +template +class Signal2 : public SignalBase< SignalHandler2 > +{ +typedef SignalBase< SignalHandler2 > Base; +public: +void operator()( FirstArgument a1, SecondArgument a2 ) const { + invokeSignalHandlers( Base::begin(), Base::end(), FunctorInvoke( a1, a2 ) ); +} +}; + +template +class Signal3 : public SignalBase< SignalHandler3 > +{ +typedef SignalBase< SignalHandler3 > Base; +public: +void operator()( FirstArgument a1, SecondArgument a2, ThirdArgument a3 ) const { + invokeSignalHandlers( Base::begin(), Base::end(), FunctorInvoke( a1, a2, a3 ) ); +} +}; + +#endif diff --git a/libs/signal/signalfwd.h b/libs/signal/signalfwd.h new file mode 100644 index 0000000..507c0e7 --- /dev/null +++ b/libs/signal/signalfwd.h @@ -0,0 +1,44 @@ + +#if !defined( INCLUDED_SIGNALFWD_H ) +#define INCLUDED_SIGNALFWD_H + +class SignalHandler; +template +class SignalHandler1; +template +class SignalHandler2; +template +class SignalHandler3; + +template +class Opaque; + +///\brief A pointer that always has a well-defined value. +/// If no value is specified, the appropriate null value is used. +template +class Handle +{ +Type* p; +public: +Handle() : p( 0 ){ +} +explicit Handle( Type* p ) : p( p ){ +} +Type* get() const { + return p; +} +bool isNull() const { + return p == 0; +} +}; + +template +class SignalFwd +{ +public: +typedef Handle< Opaque > handler_id_type; +}; + +typedef SignalFwd::handler_id_type SignalHandlerId; + +#endif diff --git a/libs/splines/CMakeLists.txt b/libs/splines/CMakeLists.txt new file mode 100644 index 0000000..3a6b603 --- /dev/null +++ b/libs/splines/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(splines + math_angles.cpp math_angles.h + math_matrix.cpp math_matrix.h + math_quaternion.cpp math_quaternion.h + math_vector.cpp math_vector.h + q_parse.cpp + q_shared.cpp q_shared.h + splines.cpp splines.h + util_list.h + util_str.cpp util_str.h + ) diff --git a/libs/splines/math_angles.cpp b/libs/splines/math_angles.cpp new file mode 100644 index 0000000..81b7f21 --- /dev/null +++ b/libs/splines/math_angles.cpp @@ -0,0 +1,152 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "q_shared.h" +#include + +angles_t ang_zero( 0.0f, 0.0f, 0.0f ); + +void toAngles( mat3_t &src, angles_t &dst ) { + double theta; + double cp; + double sp; + + sp = src[ 0 ][ 2 ]; + + // cap off our sin value so that we don't get any NANs + if ( sp > 1.0 ) { + sp = 1.0; + } + else if ( sp < -1.0 ) { + sp = -1.0; + } + + theta = -asin( sp ); + cp = cos( theta ); + + if ( cp > 8192 * FLT_EPSILON ) { + dst.pitch = theta * 180 / M_PI; + dst.yaw = atan2( src[ 0 ][ 1 ], src[ 0 ][ 0 ] ) * 180 / M_PI; + dst.roll = atan2( src[ 1 ][ 2 ], src[ 2 ][ 2 ] ) * 180 / M_PI; + } + else { + dst.pitch = theta * 180 / M_PI; + dst.yaw = -atan2( src[ 1 ][ 0 ], src[ 1 ][ 1 ] ) * 180 / M_PI; + dst.roll = 0; + } +} + +void toAngles( quat_t &src, angles_t &dst ) { + mat3_t temp; + + toMatrix( src, temp ); + toAngles( temp, dst ); +} + +void toAngles( idVec3 &src, angles_t &dst ) { + dst.pitch = src[ 0 ]; + dst.yaw = src[ 1 ]; + dst.roll = src[ 2 ]; +} + +void angles_t::toVectors( idVec3 *forward, idVec3 *right, idVec3 *up ) { + float angle; + static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs + + angle = yaw * ( M_PI * 2 / 360 ); + sy = sin( angle ); + cy = cos( angle ); + + angle = pitch * ( M_PI * 2 / 360 ); + sp = sin( angle ); + cp = cos( angle ); + + angle = roll * ( M_PI * 2 / 360 ); + sr = sin( angle ); + cr = cos( angle ); + + if ( forward ) { + forward->set( cp * cy, cp * sy, -sp ); + } + + if ( right ) { + right->set( -sr * sp * cy + cr * sy, -sr * sp * sy + -cr * cy, -sr * cp ); + } + + if ( up ) { + up->set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + } +} + +idVec3 angles_t::toForward( void ) { + float angle; + static float sp, sy, cp, cy; // static to help MS compiler fp bugs + + angle = yaw * ( M_PI * 2 / 360 ); + sy = sin( angle ); + cy = cos( angle ); + + angle = pitch * ( M_PI * 2 / 360 ); + sp = sin( angle ); + cp = cos( angle ); + + return idVec3( cp * cy, cp * sy, -sp ); +} + +/* + ================= + Normalize360 + + returns angles normalized to the range [0 <= angle < 360] + ================= + */ +angles_t& angles_t::Normalize360( void ) { + pitch = ( 360.0 / 65536 ) * ( ( int )( pitch * ( 65536 / 360.0 ) ) & 65535 ); + yaw = ( 360.0 / 65536 ) * ( ( int )( yaw * ( 65536 / 360.0 ) ) & 65535 ); + roll = ( 360.0 / 65536 ) * ( ( int )( roll * ( 65536 / 360.0 ) ) & 65535 ); + + return *this; +} + + +/* + ================= + Normalize180 + + returns angles normalized to the range [-180 < angle <= 180] + ================= + */ +angles_t& angles_t::Normalize180( void ) { + Normalize360(); + + if ( pitch > 180.0 ) { + pitch -= 360.0; + } + + if ( yaw > 180.0 ) { + yaw -= 360.0; + } + + if ( roll > 180.0 ) { + roll -= 360.0; + } + return *this; +} diff --git a/libs/splines/math_angles.h b/libs/splines/math_angles.h new file mode 100644 index 0000000..f356b56 --- /dev/null +++ b/libs/splines/math_angles.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 + */ + +#ifndef __MATH_ANGLES_H__ +#define __MATH_ANGLES_H__ + +#include +#include + +#include "math_vector.h" + +class mat3_t; +class quat_t; +class idVec3; +typedef idVec3 &vec3_p; + +class angles_t { +public: +float pitch; +float yaw; +float roll; + +angles_t(); +angles_t( float pitch, float yaw, float roll ); +angles_t( const idVec3 &vec ); + +friend void toAngles( idVec3 &src, angles_t &dst ); +friend void toAngles( quat_t &src, angles_t &dst ); +friend void toAngles( mat3_t &src, angles_t &dst ); + +operator vec3_p(); + +float operator[]( int index ) const; +float& operator[]( int index ); + +void set( float pitch, float yaw, float roll ); + +void operator=( angles_t const &a ); +void operator=( idVec3 const &a ); + +friend angles_t operator+( const angles_t &a, const angles_t &b ); +angles_t &operator+=( angles_t const &a ); +angles_t &operator+=( idVec3 const &a ); + +friend angles_t operator-( angles_t &a, angles_t &b ); +angles_t &operator-=( angles_t &a ); + +friend angles_t operator*( const angles_t &a, float b ); +friend angles_t operator*( float a, const angles_t &b ); +angles_t &operator*=( float a ); + +friend int operator==( angles_t &a, angles_t &b ); + +friend int operator!=( angles_t &a, angles_t &b ); + +void toVectors( idVec3 *forward, idVec3 *right = NULL, idVec3 *up = NULL ); +idVec3 toForward( void ); + +angles_t &Zero( void ); + +angles_t &Normalize360( void ); +angles_t &Normalize180( void ); +}; + +extern angles_t ang_zero; + +inline angles_t::angles_t() {} + +inline angles_t::angles_t( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +inline angles_t::angles_t( const idVec3 &vec ) { + this->pitch = vec.x; + this->yaw = vec.y; + this->roll = vec.z; +} + +inline float angles_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +inline float& angles_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +inline angles_t::operator vec3_p( void ) { + return *( idVec3 * )&pitch; +} + +inline void angles_t::set( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +inline void angles_t::operator=( angles_t const &a ) { + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; +} + +inline void angles_t::operator=( idVec3 const &a ) { + pitch = a[ 0 ]; + yaw = a[ 1 ]; + roll = a[ 2 ]; +} + +inline angles_t operator+( const angles_t &a, const angles_t &b ) { + return angles_t( a.pitch + b.pitch, a.yaw + b.yaw, a.roll + b.roll ); +} + +inline angles_t& angles_t::operator+=( angles_t const &a ) { + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + + return *this; +} + +inline angles_t& angles_t::operator+=( idVec3 const &a ) { + pitch += a.x; + yaw += a.y; + roll += a.z; + + return *this; +} + +inline angles_t operator-( angles_t &a, angles_t &b ) { + return angles_t( a.pitch - b.pitch, a.yaw - b.yaw, a.roll - b.roll ); +} + +inline angles_t& angles_t::operator-=( angles_t &a ) { + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + + return *this; +} + +inline angles_t operator*( const angles_t &a, float b ) { + return angles_t( a.pitch * b, a.yaw * b, a.roll * b ); +} + +inline angles_t operator*( float a, const angles_t &b ) { + return angles_t( a * b.pitch, a * b.yaw, a * b.roll ); +} + +inline angles_t& angles_t::operator*=( float a ) { + pitch *= a; + yaw *= a; + roll *= a; + + return *this; +} + +inline int operator==( angles_t &a, angles_t &b ) { + return ( ( a.pitch == b.pitch ) && ( a.yaw == b.yaw ) && ( a.roll == b.roll ) ); +} + +inline int operator!=( angles_t &a, angles_t &b ) { + return ( ( a.pitch != b.pitch ) || ( a.yaw != b.yaw ) || ( a.roll != b.roll ) ); +} + +inline angles_t& angles_t::Zero( void ) { + pitch = 0.0f; + yaw = 0.0f; + roll = 0.0f; + + return *this; +} + +#endif /* !__MATH_ANGLES_H__ */ diff --git a/libs/splines/math_matrix.cpp b/libs/splines/math_matrix.cpp new file mode 100644 index 0000000..0f6f7c4 --- /dev/null +++ b/libs/splines/math_matrix.cpp @@ -0,0 +1,134 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "q_shared.h" + +mat3_t mat3_default( idVec3( 1, 0, 0 ), idVec3( 0, 1, 0 ), idVec3( 0, 0, 1 ) ); + +void toMatrix( quat_t const &src, mat3_t &dst ) { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + + x2 = src.x + src.x; + y2 = src.y + src.y; + z2 = src.z + src.z; + + xx = src.x * x2; + xy = src.x * y2; + xz = src.x * z2; + + yy = src.y * y2; + yz = src.y * z2; + zz = src.z * z2; + + wx = src.w * x2; + wy = src.w * y2; + wz = src.w * z2; + + dst[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + dst[ 0 ][ 1 ] = xy - wz; + dst[ 0 ][ 2 ] = xz + wy; + + dst[ 1 ][ 0 ] = xy + wz; + dst[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + dst[ 1 ][ 2 ] = yz - wx; + + dst[ 2 ][ 0 ] = xz - wy; + dst[ 2 ][ 1 ] = yz + wx; + dst[ 2 ][ 2 ] = 1.0f - ( xx + yy ); +} + +void toMatrix( angles_t const &src, mat3_t &dst ) { + float angle; + static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs + + angle = src.yaw * ( M_PI * 2.0f / 360.0f ); + sy = sin( angle ); + cy = cos( angle ); + + angle = src.pitch * ( M_PI * 2.0f / 360.0f ); + sp = sin( angle ); + cp = cos( angle ); + + angle = src.roll * ( M_PI * 2.0f / 360.0f ); + sr = sin( angle ); + cr = cos( angle ); + + dst[ 0 ].set( cp * cy, cp * sy, -sp ); + dst[ 1 ].set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + dst[ 2 ].set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); +} + +void toMatrix( idVec3 const &src, mat3_t &dst ) { + angles_t sup = src; + toMatrix( sup, dst ); +} + +void mat3_t::ProjectVector( const idVec3 &src, idVec3 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; +} + +void mat3_t::UnprojectVector( const idVec3 &src, idVec3 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z; +} + +void mat3_t::Transpose( mat3_t &matrix ) { + int i; + int j; + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + matrix[ i ][ j ] = mat[ j ][ i ]; + } + } +} + +void mat3_t::Transpose( void ) { + float temp; + int i; + int j; + + for ( i = 0; i < 3; i++ ) { + for ( j = i + 1; j < 3; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } +} + +mat3_t mat3_t::Inverse( void ) const { + mat3_t inv( *this ); + + inv.Transpose(); + + return inv; +} + +void mat3_t::Clear( void ) { + mat[0].set( 1, 0, 0 ); + mat[1].set( 0, 1, 0 ); + mat[2].set( 0, 0, 1 ); +} diff --git a/libs/splines/math_matrix.h b/libs/splines/math_matrix.h new file mode 100644 index 0000000..b4427dc --- /dev/null +++ b/libs/splines/math_matrix.h @@ -0,0 +1,215 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 __MATH_MATRIX_H__ +#define __MATH_MATRIX_H__ + +#include +#include "math_vector.h" + +class quat_t; +class angles_t; + +class mat3_t { +public: +idVec3 mat[ 3 ]; + +mat3_t(); +mat3_t( float src[ 3 ][ 3 ] ); +mat3_t( idVec3 const &x, idVec3 const &y, idVec3 const &z ); +mat3_t( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ); + +friend void toMatrix( quat_t const &src, mat3_t &dst ); +friend void toMatrix( angles_t const &src, mat3_t &dst ); +friend void toMatrix( idVec3 const &src, mat3_t &dst ); + +idVec3 operator[]( int index ) const; +idVec3 &operator[]( int index ); + +idVec3 operator*( const idVec3 &vec ) const; +mat3_t operator*( const mat3_t &a ) const; +mat3_t operator*( float a ) const; +mat3_t operator+( mat3_t const &a ) const; +mat3_t operator-( mat3_t const &a ) const; + +friend idVec3 operator*( const idVec3 &vec, const mat3_t &mat ); +friend mat3_t operator*( float a, mat3_t const &b ); + +mat3_t &operator*=( float a ); +mat3_t &operator+=( mat3_t const &a ); +mat3_t &operator-=( mat3_t const &a ); + +void Clear( void ); + +void ProjectVector( const idVec3 &src, idVec3 &dst ) const; +void UnprojectVector( const idVec3 &src, idVec3 &dst ) const; + +void OrthoNormalize( void ); +void Transpose( mat3_t &matrix ); +void Transpose( void ); +mat3_t Inverse( void ) const; +void Identity( void ); + +friend void InverseMultiply( const mat3_t &inv, const mat3_t &b, mat3_t &dst ); +friend mat3_t SkewSymmetric( idVec3 const &src ); +}; + +ID_INLINE mat3_t::mat3_t() { +} + +ID_INLINE mat3_t::mat3_t(float src[3][3]) { + memcpy(mat, src, sizeof(float) * 3 * 3); +} + +ID_INLINE mat3_t::mat3_t( idVec3 const &x, idVec3 const &y, idVec3 const &z ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; mat[ 0 ].z = x.z; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; mat[ 1 ].z = y.z; + mat[ 2 ].x = z.x; mat[ 2 ].y = z.y; mat[ 2 ].z = z.z; +} + +ID_INLINE mat3_t::mat3_t( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; mat[ 0 ].z = xz; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; mat[ 1 ].z = yz; + mat[ 2 ].x = zx; mat[ 2 ].y = zy; mat[ 2 ].z = zz; +} + +ID_INLINE idVec3 mat3_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3& mat3_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3 mat3_t::operator*( const idVec3 &vec ) const { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE mat3_t mat3_t::operator*( const mat3_t &a ) const { + return mat3_t( + mat[0].x * a[0].x + mat[0].y * a[1].x + mat[0].z * a[2].x, + mat[0].x * a[0].y + mat[0].y * a[1].y + mat[0].z * a[2].y, + mat[0].x * a[0].z + mat[0].y * a[1].z + mat[0].z * a[2].z, + mat[1].x * a[0].x + mat[1].y * a[1].x + mat[1].z * a[2].x, + mat[1].x * a[0].y + mat[1].y * a[1].y + mat[1].z * a[2].y, + mat[1].x * a[0].z + mat[1].y * a[1].z + mat[1].z * a[2].z, + mat[2].x * a[0].x + mat[2].y * a[1].x + mat[2].z * a[2].x, + mat[2].x * a[0].y + mat[2].y * a[1].y + mat[2].z * a[2].y, + mat[2].x * a[0].z + mat[2].y * a[1].z + mat[2].z * a[2].z ); +} + +ID_INLINE mat3_t mat3_t::operator*( float a ) const { + return mat3_t( + mat[0].x * a, mat[0].y * a, mat[0].z * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a ); +} + +ID_INLINE mat3_t mat3_t::operator+( mat3_t const &a ) const { + return mat3_t( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z ); +} + +ID_INLINE mat3_t mat3_t::operator-( mat3_t const &a ) const { + return mat3_t( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z ); +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const mat3_t &mat ) { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE mat3_t operator*( float a, mat3_t const &b ) { + return mat3_t( + b[0].x * a, b[0].y * a, b[0].z * a, + b[1].x * a, b[1].y * a, b[1].z * a, + b[2].x * a, b[2].y * a, b[2].z * a ); +} + +ID_INLINE mat3_t &mat3_t::operator*=( float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; + + return *this; +} + +ID_INLINE mat3_t &mat3_t::operator+=( mat3_t const &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; + + return *this; +} + +ID_INLINE mat3_t &mat3_t::operator-=( mat3_t const &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; + + return *this; +} + +ID_INLINE void mat3_t::OrthoNormalize( void ) { + mat[ 0 ].Normalize(); + mat[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + mat[ 2 ].Normalize(); + mat[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + mat[ 1 ].Normalize(); +} + +ID_INLINE void mat3_t::Identity( void ) { + mat[ 0 ].x = 1.f; mat[ 0 ].y = 0.f; mat[ 0 ].z = 0.f; + mat[ 1 ].x = 0.f; mat[ 1 ].y = 1.f; mat[ 1 ].z = 0.f; + mat[ 2 ].x = 0.f; mat[ 2 ].y = 0.f; mat[ 2 ].z = 1.f; +} + +ID_INLINE void InverseMultiply( const mat3_t &inv, const mat3_t &b, mat3_t &dst ) { + dst[0].x = inv[0].x * b[0].x + inv[1].x * b[1].x + inv[2].x * b[2].x; + dst[0].y = inv[0].x * b[0].y + inv[1].x * b[1].y + inv[2].x * b[2].y; + dst[0].z = inv[0].x * b[0].z + inv[1].x * b[1].z + inv[2].x * b[2].z; + dst[1].x = inv[0].y * b[0].x + inv[1].y * b[1].x + inv[2].y * b[2].x; + dst[1].y = inv[0].y * b[0].y + inv[1].y * b[1].y + inv[2].y * b[2].y; + dst[1].z = inv[0].y * b[0].z + inv[1].y * b[1].z + inv[2].y * b[2].z; + dst[2].x = inv[0].z * b[0].x + inv[1].z * b[1].x + inv[2].z * b[2].x; + dst[2].y = inv[0].z * b[0].y + inv[1].z * b[1].y + inv[2].z * b[2].y; + dst[2].z = inv[0].z * b[0].z + inv[1].z * b[1].z + inv[2].z * b[2].z; +} + +ID_INLINE mat3_t SkewSymmetric( idVec3 const &src ) { + return mat3_t( 0.0f, -src.z, src.y, src.z, 0.0f, -src.x, -src.y, src.x, 0.0f ); +} + +extern mat3_t mat3_default; + +#endif /* !__MATH_MATRIX_H__ */ diff --git a/libs/splines/math_quaternion.cpp b/libs/splines/math_quaternion.cpp new file mode 100644 index 0000000..f37f879 --- /dev/null +++ b/libs/splines/math_quaternion.cpp @@ -0,0 +1,79 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "math_quaternion.h" +#include "math_matrix.h" + +void toQuat( idVec3 &src, quat_t &dst ) { + dst.x = src.x; + dst.y = src.y; + dst.z = src.z; + dst.w = 0.0f; +} + +void toQuat( angles_t &src, quat_t &dst ) { + mat3_t temp; + + toMatrix( src, temp ); + toQuat( temp, dst ); +} + +void toQuat( mat3_t &src, quat_t &dst ) { + float trace; + float s; + int i; + int j; + int k; + + static int next[ 3 ] = { 1, 2, 0 }; + + trace = src[ 0 ][ 0 ] + src[ 1 ][ 1 ] + src[ 2 ][ 2 ]; + if ( trace > 0.0f ) { + s = ( float )sqrt( trace + 1.0f ); + dst.w = s * 0.5f; + s = 0.5f / s; + + dst.x = ( src[ 2 ][ 1 ] - src[ 1 ][ 2 ] ) * s; + dst.y = ( src[ 0 ][ 2 ] - src[ 2 ][ 0 ] ) * s; + dst.z = ( src[ 1 ][ 0 ] - src[ 0 ][ 1 ] ) * s; + } + else { + i = 0; + if ( src[ 1 ][ 1 ] > src[ 0 ][ 0 ] ) { + i = 1; + } + if ( src[ 2 ][ 2 ] > src[ i ][ i ] ) { + i = 2; + } + + j = next[ i ]; + k = next[ j ]; + + s = ( float )sqrt( ( src[ i ][ i ] - ( src[ j ][ j ] + src[ k ][ k ] ) ) + 1.0f ); + dst[ i ] = s * 0.5f; + + s = 0.5f / s; + + dst.w = ( src[ k ][ j ] - src[ j ][ k ] ) * s; + dst[ j ] = ( src[ j ][ i ] + src[ i ][ j ] ) * s; + dst[ k ] = ( src[ k ][ i ] + src[ i ][ k ] ) * s; + } +} diff --git a/libs/splines/math_quaternion.h b/libs/splines/math_quaternion.h new file mode 100644 index 0000000..b33f442 --- /dev/null +++ b/libs/splines/math_quaternion.h @@ -0,0 +1,190 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 __MATH_QUATERNION_H__ +#define __MATH_QUATERNION_H__ + +#include +#include + +class idVec3_t; +class angles_t; +class mat3_t; + +class quat_t { +public: +float x; +float y; +float z; +float w; + +quat_t(); +quat_t( float x, float y, float z, float w ); + +friend void toQuat( idVec3_t &src, quat_t &dst ); +friend void toQuat( angles_t &src, quat_t &dst ); +friend void toQuat( mat3_t &src, quat_t &dst ); + +float *vec4( void ); + +float operator[]( int index ) const; +float &operator[]( int index ); + +void set( float x, float y, float z, float w ); + +void operator=( quat_t a ); + +friend quat_t operator+( quat_t a, quat_t b ); +quat_t &operator+=( quat_t a ); + +friend quat_t operator-( quat_t a, quat_t b ); +quat_t &operator-=( quat_t a ); + +friend quat_t operator*( quat_t a, float b ); +friend quat_t operator*( float a, quat_t b ); +quat_t &operator*=( float a ); + +friend int operator==( quat_t a, quat_t b ); +friend int operator!=( quat_t a, quat_t b ); + +float Length( void ); +quat_t &Normalize( void ); + +quat_t operator-(); +}; + +inline quat_t::quat_t() { +} + +inline quat_t::quat_t( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +inline float *quat_t::vec4( void ) { + return &x; +} + +inline float quat_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline float& quat_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline void quat_t::set( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +inline void quat_t::operator=( quat_t a ) { + x = a.x; + y = a.y; + z = a.z; + w = a.w; +} + +inline quat_t operator+( quat_t a, quat_t b ) { + return quat_t( a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w ); +} + +inline quat_t& quat_t::operator+=( quat_t a ) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +inline quat_t operator-( quat_t a, quat_t b ) { + return quat_t( a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w ); +} + +inline quat_t& quat_t::operator-=( quat_t a ) { + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +inline quat_t operator*( quat_t a, float b ) { + return quat_t( a.x * b, a.y * b, a.z * b, a.w * b ); +} + +inline quat_t operator*( float a, quat_t b ) { + return b * a; +} + +inline quat_t& quat_t::operator*=( float a ) { + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +inline int operator==( quat_t a, quat_t b ) { + return ( ( a.x == b.x ) && ( a.y == b.y ) && ( a.z == b.z ) && ( a.w == b.w ) ); +} + +inline int operator!=( quat_t a, quat_t b ) { + return ( ( a.x != b.x ) || ( a.y != b.y ) || (( a.z != b.z ) && ( a.w != b.w )) ); +} + +inline float quat_t::Length( void ) { + float length; + + length = x * x + y * y + z * z + w * w; + return ( float )sqrt( length ); +} + +inline quat_t& quat_t::Normalize( void ) { + float length; + float ilength; + + length = this->Length(); + if ( length ) { + ilength = 1 / length; + x *= ilength; + y *= ilength; + z *= ilength; + w *= ilength; + } + + return *this; +} + +inline quat_t quat_t::operator-() { + return quat_t( -x, -y, -z, -w ); +} + +#endif /* !__MATH_QUATERNION_H__ */ diff --git a/libs/splines/math_vector.cpp b/libs/splines/math_vector.cpp new file mode 100644 index 0000000..572f903 --- /dev/null +++ b/libs/splines/math_vector.cpp @@ -0,0 +1,147 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "math_vector.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h + +#define LERP_DELTA 1e-6 + +idVec3 vec_zero( 0.0f, 0.0f, 0.0f ); + +Bounds boundsZero; + +float idVec3::toYaw( void ) { + float yaw; + + if ( ( y == 0 ) && ( x == 0 ) ) { + yaw = 0; + } + else { + yaw = atan2( y, x ) * 180 / M_PI; + if ( yaw < 0 ) { + yaw += 360; + } + } + + return yaw; +} + +float idVec3::toPitch( void ) { + float forward; + float pitch; + + if ( ( x == 0 ) && ( y == 0 ) ) { + if ( z > 0 ) { + pitch = 90; + } + else { + pitch = 270; + } + } + else { + forward = ( float )idSqrt( x * x + y * y ); + pitch = atan2( z, forward ) * 180 / M_PI; + if ( pitch < 0 ) { + pitch += 360; + } + } + + return pitch; +} + +/* + angles_t idVec3::toAngles( void ) { + float forward; + float yaw; + float pitch; + + if ( ( x == 0 ) && ( y == 0 ) ) { + yaw = 0; + if ( z > 0 ) { + pitch = 90; + } else { + pitch = 270; + } + } else { + yaw = atan2( y, x ) * 180 / M_PI; + if ( yaw < 0 ) { + yaw += 360; + } + + forward = ( float )idSqrt( x * x + y * y ); + pitch = atan2( z, forward ) * 180 / M_PI; + if ( pitch < 0 ) { + pitch += 360; + } + } + + return angles_t( -pitch, yaw, 0 ); + } + */ + +idVec3 LerpVector( idVec3 &w1, idVec3 &w2, const float t ) { + float omega, cosom, sinom, scale0, scale1; + + cosom = w1 * w2; + if ( ( 1.0 - cosom ) > LERP_DELTA ) { + omega = acos( cosom ); + sinom = sin( omega ); + scale0 = sin( ( 1.0 - t ) * omega ) / sinom; + scale1 = sin( t * omega ) / sinom; + } + else { + scale0 = 1.0 - t; + scale1 = t; + } + + return ( w1 * scale0 + w2 * scale1 ); +} + +/* + ============= + idVec3::string + + This is just a convenience function + for printing vectors + ============= + */ +char *idVec3::string( void ) { + static int index = 0; + static char str[ 8 ][ 36 ]; + char *s; + + // use an array so that multiple toString's won't collide + s = str[ index ]; + index = ( index + 1 ) & 7; + + sprintf( s, "%.2f %.2f %.2f", x, y, z ); + + return s; +} diff --git a/libs/splines/math_vector.h b/libs/splines/math_vector.h new file mode 100644 index 0000000..4fbe8d9 --- /dev/null +++ b/libs/splines/math_vector.h @@ -0,0 +1,576 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 __MATH_VECTOR_H__ +#define __MATH_VECTOR_H__ + +#include "globaldefs.h" + +#if GDEF_COMPILER_MSVC +#pragma warning(disable : 4244) +#endif + +#include +#include + +//#define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +//#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +//#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +//#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +//#define VectorCopy(a,b) ((b).x=(a).x,(b).y=(a).y,(b).z=(a).z]) + +//#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define __VectorMA( 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 ) ) +//#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 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 VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) +#define VectorNegate( a,b ) ( ( b )[0] = -( a )[0],( b )[1] = -( a )[1],( b )[2] = -( a )[2] ) +//#define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z)) +#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]; } + + +//#include "util_heap.h" + +#ifndef EQUAL_EPSILON +#define EQUAL_EPSILON 0.001 +#endif + +float Q_fabs( float f ); + +#ifndef ID_INLINE +#define ID_INLINE GDEF_ATTRIBUTE_INLINE +#endif + +// if this is defined, vec3 will take four elements, which may allow +// easier SIMD optimizations +//#define FAT_VEC3 +//#ifdef __ppc__ +//#pragma align(16) +//#endif + +class angles_t; +#ifdef __ppc__ +// Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, +// runs *much* faster than calling sqrt(). We'll use two Newton-Raphson +// refinement steps to get bunch more precision in the 1/sqrt() value for very little cost. +// We'll then multiply 1/sqrt times the original value to get the sqrt. +// This is about 12.4 times faster than sqrt() and according to my testing (not exhaustive) +// it returns fairly accurate results (error below 1.0e-5 up to 100000.0 in 0.1 increments). + +static inline float idSqrt( float x ) { + const float half = 0.5; + const float one = 1.0; + float B, y0, y1; + + // This'll NaN if it hits frsqrte. Handle both +0.0 and -0.0 + if ( fabs( x ) == 0.0 ) { + return x; + } + B = x; + +#if GDEF_COMPILER_GNU + asm ( "frsqrte %0,%1" : "=f" ( y0 ) : "f" ( B ) ); +#else + y0 = __frsqrte( B ); +#endif + /* First refinement step */ + + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Second refinement step -- copy the output of the last step to the input of this step */ + + y0 = y1; + y1 = y0 + half * y0 * ( one - B * y0 * y0 ); + + /* Get sqrt(x) from x * 1/sqrt(x) */ + return x * y1; +} +#else +static inline double idSqrt( double x ) { + return sqrt( x ); +} +#endif + + +//class idVec3 : public idHeap { +class idVec3 { +public: +#ifndef FAT_VEC3 +float x,y,z; +#else +float x,y,z,dist; +#endif + +#ifndef FAT_VEC3 +idVec3() {}; +#else +idVec3() {dist = 0.0f; }; +#endif +idVec3( const float x, const float y, const float z ); + +operator float *(); + +float operator[]( const int index ) const; +float &operator[]( const int index ); + +void set( const float x, const float y, const float z ); + +idVec3 operator-() const; + +idVec3 &operator=( const idVec3 &a ); + +float operator*( const idVec3 &a ) const; +idVec3 operator*( const float a ) const; +friend idVec3 operator*( float a, idVec3 b ); + +idVec3 operator+( const idVec3 &a ) const; +idVec3 operator-( const idVec3 &a ) const; + +idVec3 &operator+=( const idVec3 &a ); +idVec3 &operator-=( const idVec3 &a ); +idVec3 &operator*=( const float a ); + +int operator==( const idVec3 &a ) const; +int operator!=( const idVec3 &a ) const; + +idVec3 Cross( const idVec3 &a ) const; +idVec3 &Cross( const idVec3 &a, const idVec3 &b ); + +float Length( void ) const; +float Normalize( void ); + +void Zero( void ); +void Snap( void ); +void SnapTowards( const idVec3 &to ); + +float toYaw( void ); +float toPitch( void ); +angles_t toAngles( void ); +friend idVec3 LerpVector( const idVec3 &w1, const idVec3 &w2, const float t ); + +char *string( void ); +}; + +extern idVec3 vec_zero; + +ID_INLINE idVec3::idVec3( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +#ifdef FAT_VEC3 + this->dist = 0.0f; +#endif +} + +ID_INLINE float idVec3::operator[]( const int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float &idVec3::operator[]( const int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec3::operator float *( void ) { + return &x; +} + +ID_INLINE idVec3 idVec3::operator-() const { + return idVec3( -x, -y, -z ); +} + +ID_INLINE idVec3 &idVec3::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + + return *this; +} + +ID_INLINE void idVec3::set( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE idVec3 idVec3::operator-( const idVec3 &a ) const { + return idVec3( x - a.x, y - a.y, z - a.z ); +} + +ID_INLINE float idVec3::operator*( const idVec3 &a ) const { + return x * a.x + y * a.y + z * a.z; +} + +ID_INLINE idVec3 idVec3::operator*( const float a ) const { + return idVec3( x * a, y * a, z * a ); +} + +ID_INLINE idVec3 operator*( const float a, const idVec3 b ) { + return idVec3( b.x * a, b.y * a, b.z * a ); +} + +ID_INLINE idVec3 idVec3::operator+( const idVec3 &a ) const { + return idVec3( x + a.x, y + a.y, z + a.z ); +} + +ID_INLINE idVec3 &idVec3::operator+=( const idVec3 &a ) { + x += a.x; + y += a.y; + z += a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator-=( const idVec3 &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator*=( const float a ) { + x *= a; + y *= a; + z *= a; + + return *this; +} + +ID_INLINE int idVec3::operator==( const idVec3 &a ) const { + if ( Q_fabs( x - a.x ) > EQUAL_EPSILON ) { + return false; + } + + if ( Q_fabs( y - a.y ) > EQUAL_EPSILON ) { + return false; + } + + if ( Q_fabs( z - a.z ) > EQUAL_EPSILON ) { + return false; + } + + return true; +} + +ID_INLINE int idVec3::operator!=( const idVec3 &a ) const { + if ( Q_fabs( x - a.x ) > EQUAL_EPSILON ) { + return true; + } + + if ( Q_fabs( y - a.y ) > EQUAL_EPSILON ) { + return true; + } + + if ( Q_fabs( z - a.z ) > EQUAL_EPSILON ) { + return true; + } + + return false; +} + +ID_INLINE idVec3 idVec3::Cross( const idVec3 &a ) const { + return idVec3( y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x ); +} + +ID_INLINE idVec3 &idVec3::Cross( const idVec3 &a, const idVec3 &b ) { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + + return *this; +} + +ID_INLINE float idVec3::Length( void ) const { + float length; + + length = x * x + y * y + z * z; + return ( float )idSqrt( length ); +} + +ID_INLINE float idVec3::Normalize( void ) { + float length; + float ilength; + + length = this->Length(); + if ( length ) { + ilength = 1.0f / length; + x *= ilength; + y *= ilength; + z *= ilength; + } + + return length; +} + +ID_INLINE void idVec3::Zero( void ) { + x = 0.0f; + y = 0.0f; + z = 0.0f; +} + +ID_INLINE void idVec3::Snap( void ) { + x = float( int( x ) ); + y = float( int( y ) ); + z = float( int( z ) ); +} + +/* + ====================== + SnapTowards + + Round a vector to integers for more efficient network + transmission, but make sure that it rounds towards a given point + rather than blindly truncating. This prevents it from truncating + into a wall. + ====================== + */ +ID_INLINE void idVec3::SnapTowards( const idVec3 &to ) { + if ( to.x <= x ) { + x = float( int( x ) ); + } + else { + x = float( int( x ) + 1 ); + } + + if ( to.y <= y ) { + y = float( int( y ) ); + } + else { + y = float( int( y ) + 1 ); + } + + if ( to.z <= z ) { + z = float( int( z ) ); + } + else { + z = float( int( z ) + 1 ); + } +} + +//=============================================================== + +class Bounds { +public: +idVec3 b[2]; + +Bounds(); +Bounds( const idVec3 &mins, const idVec3 &maxs ); + +void Clear(); +void Zero(); +float Radius(); // radius from origin, not from center +idVec3 Center(); +void AddPoint( const idVec3 &v ); +void AddBounds( const Bounds &bb ); +bool IsCleared(); +bool ContainsPoint( const idVec3 &p ); +bool IntersectsBounds( const Bounds &b2 ); // touching is NOT intersecting +}; + +extern Bounds boundsZero; + +ID_INLINE Bounds::Bounds(){ +} + +ID_INLINE bool Bounds::IsCleared() { + return b[0][0] > b[1][0]; +} + +ID_INLINE bool Bounds::ContainsPoint( const idVec3 &p ) { + if ( p[0] < b[0][0] || p[1] < b[0][1] || p[2] < b[0][2] + || p[0] > b[1][0] || p[1] > b[1][1] || p[2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE bool Bounds::IntersectsBounds( const Bounds &b2 ) { + if ( b2.b[1][0] < b[0][0] || b2.b[1][1] < b[0][1] || b2.b[1][2] < b[0][2] + || b2.b[0][0] > b[1][0] || b2.b[0][1] > b[1][1] || b2.b[0][2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE Bounds::Bounds( const idVec3 &mins, const idVec3 &maxs ) { + b[0] = mins; + b[1] = maxs; +} + +ID_INLINE idVec3 Bounds::Center() { + return idVec3( ( b[1][0] + b[0][0] ) * 0.5f, ( b[1][1] + b[0][1] ) * 0.5f, ( b[1][2] + b[0][2] ) * 0.5f ); +} + +ID_INLINE void Bounds::Clear() { + b[0][0] = b[0][1] = b[0][2] = 99999; + b[1][0] = b[1][1] = b[1][2] = -99999; +} + +ID_INLINE void Bounds::Zero() { + b[0][0] = b[0][1] = b[0][2] = + b[1][0] = b[1][1] = b[1][2] = 0; +} + +ID_INLINE void Bounds::AddPoint( const idVec3 &v ) { + if ( v[0] < b[0][0] ) { + b[0][0] = v[0]; + } + if ( v[0] > b[1][0] ) { + b[1][0] = v[0]; + } + if ( v[1] < b[0][1] ) { + b[0][1] = v[1]; + } + if ( v[1] > b[1][1] ) { + b[1][1] = v[1]; + } + if ( v[2] < b[0][2] ) { + b[0][2] = v[2]; + } + if ( v[2] > b[1][2] ) { + b[1][2] = v[2]; + } +} + + +ID_INLINE void Bounds::AddBounds( const Bounds &bb ) { + if ( bb.b[0][0] < b[0][0] ) { + b[0][0] = bb.b[0][0]; + } + if ( bb.b[0][1] < b[0][1] ) { + b[0][1] = bb.b[0][1]; + } + if ( bb.b[0][2] < b[0][2] ) { + b[0][2] = bb.b[0][2]; + } + + if ( bb.b[1][0] > b[1][0] ) { + b[1][0] = bb.b[1][0]; + } + if ( bb.b[1][1] > b[1][1] ) { + b[1][1] = bb.b[1][1]; + } + if ( bb.b[1][2] > b[1][2] ) { + b[1][2] = bb.b[1][2]; + } +} + +ID_INLINE float Bounds::Radius() { + int i; + float total; + float a, aa; + + total = 0; + for ( i = 0 ; i < 3 ; i++ ) { + a = (float)fabs( b[0][i] ); + aa = (float)fabs( b[1][i] ); + if ( aa > a ) { + a = aa; + } + total += a * a; + } + + return (float)idSqrt( total ); +} + +//=============================================================== + + +class idVec2 { +public: +float x; +float y; + +operator float *(); +float operator[]( int index ) const; +float &operator[]( int index ); +}; + +ID_INLINE float idVec2::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec2::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec2::operator float *( void ) { + return &x; +} + +class idVec4 : public idVec3 { +public: +#ifndef FAT_VEC3 +float dist; +#endif +idVec4(); +~idVec4() {}; + +idVec4( float x, float y, float z, float dist ); +float operator[]( int index ) const; +float &operator[]( int index ); +}; + +ID_INLINE idVec4::idVec4() {} +ID_INLINE idVec4::idVec4( float x, float y, float z, float dist ) { + this->x = x; + this->y = y; + this->z = z; + this->dist = dist; +} + +ID_INLINE float idVec4::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec4::operator[]( int index ) { + return ( &x )[ index ]; +} + + +class idVec5_t : public idVec3 { +public: +float s; +float t; +float operator[]( int index ) const; +float &operator[]( int index ); +}; + + +ID_INLINE float idVec5_t::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec5_t::operator[]( int index ) { + return ( &x )[ index ]; +} + +#endif /* !__MATH_VECTOR_H__ */ diff --git a/libs/splines/q_parse.cpp b/libs/splines/q_parse.cpp new file mode 100644 index 0000000..b856d59 --- /dev/null +++ b/libs/splines/q_parse.cpp @@ -0,0 +1,537 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// q_parse.c -- support for parsing text files + +#include "q_shared.h" + +/* + ============================================================================ + + PARSING + + ============================================================================ + */ + +// multiple character punctuation tokens +static const char *punctuation[] = { + "+=", "-=", "*=", "/=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", + NULL +}; + +typedef struct { + char token[MAX_TOKEN_CHARS]; + int lines; + qboolean ungetToken; + char parseFile[MAX_QPATH]; +} parseInfo_t; + +const int MAX_PARSE_INFO = 16; +static parseInfo_t parseInfo[MAX_PARSE_INFO]; +static int parseInfoNum; +static parseInfo_t *pi = &parseInfo[0]; + +/* + =================== + Com_BeginParseSession + =================== + */ +void Com_BeginParseSession( const char *filename ) { + if ( parseInfoNum == MAX_PARSE_INFO - 1 ) { + Com_Error( ERR_FATAL, "Com_BeginParseSession: session overflow" ); + } + parseInfoNum++; + pi = &parseInfo[parseInfoNum]; + + pi->lines = 1; + Q_strncpyz( pi->parseFile, filename, sizeof( pi->parseFile ) ); +} + +/* + =================== + Com_EndParseSession + =================== + */ +void Com_EndParseSession( void ) { + if ( parseInfoNum == 0 ) { + Com_Error( ERR_FATAL, "Com_EndParseSession: session underflow" ); + } + parseInfoNum--; + pi = &parseInfo[parseInfoNum]; +} + +/* + =================== + Com_GetCurrentParseLine + =================== + */ +int Com_GetCurrentParseLine( void ) { + return pi->lines; +} + +/* + =================== + Com_ScriptError + + Prints the script name and line number in the message + =================== + */ +void Com_ScriptError( const char *msg, ... ) { + va_list argptr; + char string[32000]; + + va_start( argptr, msg ); + vsprintf( string, msg,argptr ); + va_end( argptr ); + + Com_Error( ERR_DROP, "File %s, line %i: %s", pi->parseFile, pi->lines, string ); +} + +void Com_ScriptWarning( const char *msg, ... ) { + va_list argptr; + char string[32000]; + + va_start( argptr, msg ); + vsprintf( string, msg,argptr ); + va_end( argptr ); + + Com_Printf( "File %s, line %i: %s", pi->parseFile, pi->lines, string ); +} + + +/* + =================== + Com_UngetToken + + Calling this will make the next Com_Parse return + the current token instead of advancing the pointer + =================== + */ +void Com_UngetToken( void ) { + if ( pi->ungetToken ) { + Com_ScriptError( "UngetToken called twice" ); + } + pi->ungetToken = qtrue; +} + + +static const char *SkipWhitespace( const char (*data), qboolean *hasNewLines ) { + int c; + + while ( ( c = *data ) <= ' ' ) { + if ( !c ) { + return NULL; + } + if ( c == '\n' ) { + pi->lines++; + *hasNewLines = qtrue; + } + data++; + } + + return data; +} + +/* + ============== + Com_ParseExt + + Parse a token out of a string + Will never return NULL, just empty strings. + An empty string will only be returned at end of file. + + If "allowLineBreaks" is qtrue then an empty + string will be returned if the next token is + a newline. + ============== + */ +static char *Com_ParseExt( const char *( *data_p ), qboolean allowLineBreaks ) { + int c = 0, len; + qboolean hasNewLines = qfalse; + const char *data; + const char **punc; + + if ( !data_p ) { + Com_Error( ERR_FATAL, "Com_ParseExt: NULL data_p" ); + } + + data = *data_p; + len = 0; + pi->token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return pi->token; + } + + // skip any leading whitespace + while ( 1 ) { + // skip whitespace + data = SkipWhitespace( data, &hasNewLines ); + if ( !data ) { + *data_p = NULL; + return pi->token; + } + if ( hasNewLines && !allowLineBreaks ) { + *data_p = data; + return pi->token; + } + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) { + while ( *data && *data != '\n' ) { + data++; + } + continue; + } + + // skip /* */ comments + if ( c == '/' && data[1] == '*' ) { + while ( *data && ( *data != '*' || data[1] != '/' ) ) { + if ( *data == '\n' ) { + pi->lines++; + } + data++; + } + if ( *data ) { + data += 2; + } + continue; + } + + // a real token to parse + break; + } + + // handle quoted strings + if ( c == '\"' ) { + data++; + while ( 1 ) { + c = *data++; + if ( ( c == '\\' ) && ( *data == '\"' ) ) { + // allow quoted strings to use \" to indicate the " character + data++; + } + else if ( c == '\"' || !c ) { + pi->token[len] = 0; + *data_p = ( char * ) data; + return pi->token; + } + else if ( *data == '\n' ) { + pi->lines++; + } + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + } + } + + // check for a number + // is this parsing of negative numbers going to cause expression problems + if ( ( c >= '0' && c <= '9' ) || ( c == '-' && data[ 1 ] >= '0' && data[ 1 ] <= '9' ) || + ( c == '.' && data[ 1 ] >= '0' && data[ 1 ] <= '9' ) ) { + do { + + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( ( c >= '0' && c <= '9' ) || c == '.' ); + + // parse the exponent + if ( c == 'e' || c == 'E' ) { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + c = *data; + + if ( c == '-' || c == '+' ) { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + c = *data; + } + + do { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( c >= '0' && c <= '9' ); + } + + if ( len == MAX_TOKEN_CHARS ) { + len = 0; + } + pi->token[len] = 0; + + *data_p = ( char * ) data; + return pi->token; + } + + // check for a regular word + // we still allow forward and back slashes in name tokens for pathnames + // and also colons for drive letters + if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' || c == '/' || c == '\\' ) { + do { + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' + || ( c >= '0' && c <= '9' ) || c == '/' || c == '\\' || c == ':' || c == '.' ); + + if ( len == MAX_TOKEN_CHARS ) { + len = 0; + } + pi->token[len] = 0; + + *data_p = ( char * ) data; + return pi->token; + } + + // check for multi-character punctuation token + for ( punc = punctuation ; *punc ; punc++ ) { + int l; + int j; + + l = strlen( *punc ); + for ( j = 0 ; j < l ; j++ ) { + if ( data[j] != ( *punc )[j] ) { + break; + } + } + if ( j == l ) { + // a valid multi-character punctuation + memcpy( pi->token, *punc, l ); + pi->token[l] = 0; + data += l; + *data_p = (char *)data; + return pi->token; + } + } + + // single character punctuation + pi->token[0] = *data; + pi->token[1] = 0; + data++; + *data_p = (char *)data; + + return pi->token; +} + +/* + =================== + Com_Parse + =================== + */ +const char *Com_Parse( const char *( *data_p ) ) { + if ( pi->ungetToken ) { + pi->ungetToken = qfalse; + return pi->token; + } + return Com_ParseExt( data_p, qtrue ); +} + +/* + =================== + Com_ParseOnLine + =================== + */ +const char *Com_ParseOnLine( const char *( *data_p ) ) { + if ( pi->ungetToken ) { + pi->ungetToken = qfalse; + return pi->token; + } + return Com_ParseExt( data_p, qfalse ); +} + + + +/* + ================== + Com_MatchToken + ================== + */ +void Com_MatchToken( const char *( *buf_p ), const char *match, qboolean warning ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( strcmp( token, match ) ) { + if ( warning ) { + Com_ScriptWarning( "MatchToken: %s != %s", token, match ); + } + else { + Com_ScriptError( "MatchToken: %s != %s", token, match ); + } + } +} + + +/* + ================= + Com_SkipBracedSection + + The next token should be an open brace. + Skips until a matching close brace is found. + Internal brace depths are properly skipped. + ================= + */ +void Com_SkipBracedSection( const char *( *program ) ) { + const char *token; + int depth; + + depth = 0; + do { + token = Com_Parse( program ); + if ( token[1] == 0 ) { + if ( token[0] == '{' ) { + depth++; + } + else if ( token[0] == '}' ) { + depth--; + } + } + } while ( depth && *program ); +} + +/* + ================= + Com_SkipRestOfLine + ================= + */ +void Com_SkipRestOfLine( const char *( *data ) ) { + const char *p; + int c; + + p = *data; + while ( ( c = *p++ ) != 0 ) { + if ( c == '\n' ) { + pi->lines++; + break; + } + } + + *data = p; +} + +/* + ==================== + Com_ParseRestOfLine + ==================== + */ +const char *Com_ParseRestOfLine( const char *( *data_p ) ) { + static char line[MAX_TOKEN_CHARS]; + const char *token; + + line[0] = 0; + while ( 1 ) { + token = Com_ParseOnLine( data_p ); + if ( !token[0] ) { + break; + } + if ( line[0] ) { + Q_strcat( line, sizeof( line ), " " ); + } + Q_strcat( line, sizeof( line ), token ); + } + + return line; +} + + +float Com_ParseFloat( const char *( *buf_p ) ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( !token[0] ) { + return 0; + } + return atof( token ); +} + +int Com_ParseInt( const char *( *buf_p ) ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( !token[0] ) { + return 0; + } + return (int)atof( token ); +} + + + +void Com_Parse1DMatrix( const char *( *buf_p ), int x, float *m ) { + const char *token; + int i; + + Com_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < x ; i++ ) { + token = Com_Parse( buf_p ); + m[i] = atof( token ); + } + + Com_MatchToken( buf_p, ")" ); +} + +void Com_Parse2DMatrix( const char *( *buf_p ), int y, int x, float *m ) { + int i; + + Com_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < y ; i++ ) { + Com_Parse1DMatrix( buf_p, x, m + i * x ); + } + + Com_MatchToken( buf_p, ")" ); +} + +void Com_Parse3DMatrix( const char *( *buf_p ), int z, int y, int x, float *m ) { + int i; + + Com_MatchToken( buf_p, "(" ); + + for ( i = 0 ; i < z ; i++ ) { + Com_Parse2DMatrix( buf_p, y, x, m + i * x * y ); + } + + Com_MatchToken( buf_p, ")" ); +} diff --git a/libs/splines/q_shared.cpp b/libs/splines/q_shared.cpp new file mode 100644 index 0000000..1df4dfc --- /dev/null +++ b/libs/splines/q_shared.cpp @@ -0,0 +1,999 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// q_shared.c -- stateless support routines that are included in each code dll +#include "q_shared.h" + +/* + ============================================================================ + + GROWLISTS + + ============================================================================ + */ + +// malloc / free all in one place for debugging +extern "C" void *Com_Allocate( int bytes ); +extern "C" void Com_Dealloc( void *ptr ); + +void Com_InitGrowList( growList_t *list, int maxElements ) { + list->maxElements = maxElements; + list->currentElements = 0; + list->elements = (void **)Com_Allocate( list->maxElements * sizeof( void * ) ); +} + +int Com_AddToGrowList( growList_t *list, void *data ) { + void **old; + + if ( list->currentElements != list->maxElements ) { + list->elements[list->currentElements] = data; + return list->currentElements++; + } + + // grow, reallocate and move + old = list->elements; + + if ( list->maxElements < 0 ) { + Com_Error( ERR_FATAL, "Com_AddToGrowList: maxElements = %i", list->maxElements ); + } + + if ( list->maxElements == 0 ) { + // initialize the list to hold 100 elements + Com_InitGrowList( list, 100 ); + return Com_AddToGrowList( list, data ); + } + + list->maxElements *= 2; + + Com_DPrintf( "Resizing growlist to %i maxElements\n", list->maxElements ); + + list->elements = (void **)Com_Allocate( list->maxElements * sizeof( void * ) ); + + if ( !list->elements ) { + Com_Error( ERR_DROP, "Growlist alloc failed" ); + } + + memcpy( list->elements, old, list->currentElements * sizeof( void * ) ); + + Com_Dealloc( old ); + + return Com_AddToGrowList( list, data ); +} + +void *Com_GrowListElement( const growList_t *list, int index ) { + if ( index < 0 || index >= list->currentElements ) { + Com_Error( ERR_DROP, "Com_GrowListElement: %i out of range of %i", + index, list->currentElements ); + } + return list->elements[index]; +} + +int Com_IndexForGrowListElement( const growList_t *list, const void *element ) { + int i; + + for ( i = 0 ; i < list->currentElements ; i++ ) { + if ( list->elements[i] == element ) { + return i; + } + } + return -1; +} + +//============================================================================ + + +float Com_Clamp( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +/* + ============ + Com_StringContains + ============ + */ +const char *Com_StringContains( const char *str1, const char *str2, int casesensitive ) { + int len, i, j; + + len = strlen( str1 ) - strlen( str2 ); + for ( i = 0; i <= len; i++, str1++ ) { + for ( j = 0; str2[j]; j++ ) { + if ( casesensitive ) { + if ( str1[j] != str2[j] ) { + break; + } + } + else { + if ( toupper( str1[j] ) != toupper( str2[j] ) ) { + break; + } + } + } + if ( !str2[j] ) { + return str1; + } + } + return NULL; +} + +/* + ============ + Com_Filter + ============ + */ +int Com_Filter( const char *filter, const char *name, int casesensitive ){ + char buf[MAX_TOKEN_CHARS]; + const char *ptr; + int i, found; + + while ( *filter ) { + if ( *filter == '*' ) { + filter++; + for ( i = 0; *filter; i++ ) { + if ( *filter == '*' || *filter == '?' ) { + break; + } + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if ( strlen( buf ) ) { + ptr = Com_StringContains( name, buf, casesensitive ); + if ( !ptr ) { + return qfalse; + } + name = ptr + strlen( buf ); + } + } + else if ( *filter == '?' ) { + filter++; + name++; + } + else if ( *filter == '[' && *( filter + 1 ) == '[' ) { + filter++; + } + else if ( *filter == '[' ) { + filter++; + found = qfalse; + while ( *filter && !found ) { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + if ( *( filter + 1 ) == '-' && *( filter + 2 ) && ( *( filter + 2 ) != ']' || *( filter + 3 ) == ']' ) ) { + if ( casesensitive ) { + if ( *name >= *filter && *name <= *( filter + 2 ) ) { + found = qtrue; + } + } + else { + if ( toupper( *name ) >= toupper( *filter ) && + toupper( *name ) <= toupper( *( filter + 2 ) ) ) { + found = qtrue; + } + } + filter += 3; + } + else { + if ( casesensitive ) { + if ( *filter == *name ) { + found = qtrue; + } + } + else { + if ( toupper( *filter ) == toupper( *name ) ) { + found = qtrue; + } + } + filter++; + } + } + if ( !found ) { + return qfalse; + } + while ( *filter ) { + if ( *filter == ']' && *( filter + 1 ) != ']' ) { + break; + } + filter++; + } + filter++; + name++; + } + else { + if ( casesensitive ) { + if ( *filter != *name ) { + return qfalse; + } + } + else { + if ( toupper( *filter ) != toupper( *name ) ) { + return qfalse; + } + } + filter++; + name++; + } + } + return qtrue; +} + + +/* + ================ + Com_HashString + + ================ + */ +int Com_HashString( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while ( fname[i] != '\0' ) { + letter = tolower( fname[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + hash += (long)( letter ) * ( i + 119 ); + i++; + } + hash &= ( FILE_HASH_SIZE - 1 ); + return hash; +} + + +/* + ============ + Com_SkipPath + ============ + */ +char *Com_SkipPath( char *pathname ){ + char *last; + + last = pathname; + while ( *pathname ) + { + if ( *pathname == '/' ) { + last = pathname + 1; + } + pathname++; + } + return last; +} + +/* + ============ + Com_StripExtension + ============ + */ +void Com_StripExtension( const char *in, char *out ) { + while ( *in && *in != '.' ) { + *out++ = *in++; + } + *out = 0; +} + + +/* + ================== + Com_DefaultExtension + ================== + */ +void Com_DefaultExtension( char *path, int maxSize, const char *extension ) { + char oldPath[MAX_QPATH]; + char *src; + +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen( path ) - 1; + + while ( *src != '/' && src != path ) { + if ( *src == '.' ) { + return; // it has an extension + } + src--; + } + + Q_strncpyz( oldPath, path, sizeof( oldPath ) ); + Com_sprintf( path, maxSize, "%s%s", oldPath, extension ); +} + +/* + ============================================================================ + + BYTE ORDER FUNCTIONS + + ============================================================================ + */ + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +static short ( *_BigShort )( short l ); +static short ( *_LittleShort )( short l ); +static int ( *_BigLong )( int l ); +static int ( *_LittleLong )( int l ); +static float ( *_BigFloat )( float l ); +static float ( *_LittleFloat )( float l ); + +short BigShort( short l ){return _BigShort( l ); } +short LittleShort( short l ) {return _LittleShort( l ); } +int BigLong( int l ) {return _BigLong( l ); } +int LittleLong( int l ) {return _LittleLong( l ); } +float BigFloat( float l ) {return _BigFloat( l ); } +float LittleFloat( float l ) {return _LittleFloat( l ); } + +short ShortSwap( short l ){ + byte b1,b2; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + + return ( b1 << 8 ) + b2; +} + +short ShortNoSwap( short l ){ + return l; +} + +int LongSwap( int l ){ + byte b1,b2,b3,b4; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + b3 = ( l >> 16 ) & 255; + b4 = ( l >> 24 ) & 255; + + return ( (int)b1 << 24 ) + ( (int)b2 << 16 ) + ( (int)b3 << 8 ) + b4; +} + +int LongNoSwap( int l ){ + return l; +} + +float FloatSwap( float f ){ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap( float f ){ + return f; +} + +/* + ================ + Swap_Init + ================ + */ +void Swap_Init( void ){ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1 ) { + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + +/* + =============== + Com_ParseInfos + =============== + */ +int Com_ParseInfos( const char *buf, int max, char infos[][MAX_INFO_STRING] ) { + const char *token; + int count; + char key[MAX_TOKEN_CHARS]; + + count = 0; + + while ( 1 ) { + token = Com_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + infos[count][0] = 0; + while ( 1 ) { + token = Com_Parse( &buf ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = Com_ParseOnLine( &buf ); + if ( !token[0] ) { + token = ""; + } + Info_SetValueForKey( infos[count], key, token ); + } + count++; + } + + return count; +} + + + +/* + ============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + + ============================================================================ + */ + +int Q_isprint( int c ){ + if ( c >= 0x20 && c <= 0x7E ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_islower( int c ){ + if ( c >= 'a' && c <= 'z' ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_isupper( int c ){ + if ( c >= 'A' && c <= 'Z' ) { + return ( 1 ); + } + return ( 0 ); +} + +int Q_isalpha( int c ){ + if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) ) { + return ( 1 ); + } + return ( 0 ); +} + +char* Q_strrchr( const char* string, int c ){ + char cc = c; + char *s; + char *sp = (char *)0; + + s = (char*)string; + + while ( *s ) + { + if ( *s == cc ) { + sp = s; + } + s++; + } + if ( cc == 0 ) { + sp = s; + } + + return sp; +} + +/* + ============= + Q_strncpyz + + Safe strncpy that ensures a trailing zero + ============= + */ +void Q_strncpyz( char *dest, const char *src, std::size_t destsize ) { + if ( !src ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Com_Error( ERR_FATAL,"Q_strncpyz: destsize < 1" ); + } + + strncpy( dest, src, destsize - 1 ); + dest[destsize - 1] = 0; +} + +int Q_stricmpn( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_strncmp( const char *s1, const char *s2, int n ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + if ( c1 != c2 ) { + return c1 < c2 ? -1 : 1; + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_stricmp( const char *s1, const char *s2 ) { + return Q_stricmpn( s1, s2, 99999 ); +} + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower( *s ); + s++; + } + return s1; +} + +char *Q_strupr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = toupper( *s ); + s++; + } + return s1; +} + + +// never goes past bounds or leaves without a terminating 0 +void Q_strcat( char *dest, std::size_t size, const char *src ) { + auto l1 = strlen( dest ); + if ( l1 >= size ) { + Com_Error( ERR_FATAL, "Q_strcat: already overflowed" ); + } + Q_strncpyz( dest + l1, src, size - l1 ); +} + + +int Q_PrintStrlen( const char *string ) { + int len; + const char *p; + + if ( !string ) { + return 0; + } + + len = 0; + p = string; + while ( *p ) { + if ( Q_IsColorString( p ) ) { + p += 2; + continue; + } + p++; + len++; + } + + return len; +} + + +char *Q_CleanStr( char *string ) { + char* d; + char* s; + int c; + + s = string; + d = string; + while ( ( c = *s ) != 0 ) { + if ( Q_IsColorString( s ) ) { + s++; + } + else if ( c >= 0x20 && c <= 0x7E ) { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + + +void QDECL Com_sprintf( char *dest, std::size_t size, const char *fmt, ... ) { + va_list argptr; + char bigbuffer[32000]; // big, but small enough to fit in PPC stack + + va_start( argptr,fmt ); + int ret = vsprintf( bigbuffer,fmt,argptr ); + va_end( argptr ); + if ( ret < 0 ) { + Com_Error(ERR_FATAL, "Com_sprintf: vsprintf failed"); + } + auto len = static_cast(ret); + if ( len >= sizeof( bigbuffer ) ) { + Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" ); + } + if ( len >= size ) { + Com_Printf( "Com_sprintf: overflow of %i in %i\n", len, size ); + } + Q_strncpyz( dest, bigbuffer, size ); +} + + +/* + ============ + va + + does a varargs printf into a temp buffer, so I don't need to have + varargs versions of all text functions. + FIXME: make this buffer size safe someday + ============ + */ +char *QDECL va( const char *format, ... ) { + va_list argptr; + static char string[2][32000]; // in case va is called by nested functions + static int index = 0; + char *buf; + + buf = string[index & 1]; + index++; + + va_start( argptr, format ); + vsprintf( buf, format,argptr ); + va_end( argptr ); + + return buf; +} + + +/* + ===================================================================== + + INFO STRINGS + + ===================================================================== + */ + +/* + =============== + Info_ValueForKey + + Searches the string for the given + key and returns the associated value, or an empty string. + FIXME: overflow check? + =============== + */ +const char *Info_ValueForKey( const char *s, const char *key ) { + char pkey[MAX_INFO_KEY]; + static char value[2][MAX_INFO_VALUE]; // use two buffers so compares + // work without stomping on each other + static int valueindex = 0; + char *o; + + if ( !s || !key ) { + return ""; + } + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); + } + + valueindex ^= 1; + if ( *s == '\\' ) { + s++; + } + while ( 1 ) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) { + return ""; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while ( *s != '\\' && *s ) + { + *o++ = *s++; + } + *o = 0; + + if ( !Q_stricmp( key, pkey ) ) { + return value[valueindex]; + } + + if ( !*s ) { + break; + } + s++; + } + + return ""; +} + + +/* + =================== + Info_NextPair + + Used to itterate through all the key/value pairs in an info string + =================== + */ +void Info_NextPair( const char *( *head ), char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ) { + char *o; + const char *s; + + s = *head; + + if ( *s == '\\' ) { + s++; + } + key[0] = 0; + value[0] = 0; + + o = key; + while ( *s != '\\' ) { + if ( !*s ) { + *o = 0; + *head = s; + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) { + *o++ = *s++; + } + *o = 0; + + *head = s; +} + + +/* + =================== + Info_RemoveKey + =================== + */ +void Info_RemoveKey( char *s, const char *key ) { + char *start; + char pkey[MAX_INFO_KEY]; + char value[MAX_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); + } + + if ( strchr( key, '\\' ) ) { + return; + } + + while ( 1 ) + { + start = s; + if ( *s == '\\' ) { + s++; + } + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) + { + if ( !*s ) { + return; + } + *o++ = *s++; + } + *o = 0; + + if ( !strcmp( key, pkey ) ) { + strcpy( start, s ); // remove this part + return; + } + + if ( !*s ) { + return; + } + } + +} + + +/* + ================== + Info_Validate + + Some characters are illegal in info strings because they + can mess up the server's parsing + ================== + */ +qboolean Info_Validate( const char *s ) { + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* + ================== + Info_SetValueForKey + + Changes or adds a key/value pair + ================== + */ +void Info_SetValueForKey( char *s, const char *key, const char *value ) { + char newi[MAX_INFO_STRING]; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + if ( strchr( key, '\\' ) || strchr( value, '\\' ) ) { + Com_Printf( "Can't use keys or values with a \\\n" ); + return; + } + + if ( strchr( key, ';' ) || strchr( value, ';' ) ) { + Com_Printf( "Can't use keys or values with a semicolon\n" ); + return; + } + + if ( strchr( key, '\"' ) || strchr( value, '\"' ) ) { + Com_Printf( "Can't use keys or values with a \"\n" ); + return; + } + + Info_RemoveKey( s, key ); + if ( !value || !strlen( value ) ) { + return; + } + + Com_sprintf( newi, sizeof( newi ), "\\%s\\%s", key, value ); + + if ( strlen( newi ) + strlen( s ) > MAX_INFO_STRING ) { + Com_Printf( "Info string length exceeded\n" ); + return; + } + + strcat( s, newi ); +} + +//==================================================================== + + +/* + =============== + ParseHex + =============== + */ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} diff --git a/libs/splines/q_shared.h b/libs/splines/q_shared.h new file mode 100644 index 0000000..10a7562 --- /dev/null +++ b/libs/splines/q_shared.h @@ -0,0 +1,803 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __Q_SHARED_H +#define __Q_SHARED_H + +#include "globaldefs.h" + +// q_shared.h -- included first by ALL program modules. +// these are the definitions that have no dependance on +// central system services, and can be used by any part +// of the program without any state issues. + +// A user mod should never modify this file + +#define Q3_VERSION "DOOM 0.01" + +// alignment macros for SIMD +#define ALIGN_ON +#define ALIGN_OFF + +#if GDEF_COMPILER_MSVC + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable : 4514) +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if GDEF_OS_WINDOWS // mac doesn't have malloc.h +#include // for _alloca() +#endif +#if GDEF_COMPILER_MSVC + +//#pragma intrinsic( memset, memcpy ) + +#endif + + +// this is the define for determining if we have an asm version of a C function +#if GDEF_ARCH_BITS_32 && !defined __sun__ && !defined __LCC__ +#define id386 1 +#else +#define id386 0 +#endif + +// for windows fastcall option + +#define QDECL + +//======================= WIN32 DEFINES ================================= + +#if GDEF_OS_WINDOWS + +#define MAC_STATIC + +#undef QDECL +#define QDECL __cdecl + +// buildstring will be incorporated into the version string +#ifdef NDEBUG +#if GDEF_ARCH_BITS_32 +#define CPUSTRING "win-x86" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP" +#endif +#else +#if GDEF_ARCH_BITS_32 +#define CPUSTRING "win-x86-debug" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP-debug" +#endif +#endif + + +#define PATH_SEP '\\' + +#endif + +//======================= MAC OS X SERVER DEFINES ===================== + +#if GDEF_OS_MACOS && defined( __MACH__ ) + +#define MAC_STATIC + +#ifdef __ppc__ +#define CPUSTRING "MacOSXS-ppc" +#elif GDEF_ARCH_BITS_32 +#define CPUSTRING "MacOSXS-i386" +#else +#define CPUSTRING "MacOSXS-other" +#endif + +#define PATH_SEP '/' + +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED +#define _alloca alloca + +#undef ALIGN_ON +#undef ALIGN_OFF +#define ALIGN_ON #pragma align( 16 ) +#define ALIGN_OFF #pragma align() + +#ifdef __cplusplus +extern "C" { +#endif + +void *osxAllocateMemory( long size ); +void osxFreeMemory( void *pointer ); + +#ifdef __cplusplus +} +#endif + +#endif + +//======================= MAC DEFINES ================================= + +#ifdef __MACOS__ + +#define MAC_STATIC static + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +void Sys_PumpEvents( void ); + +#endif + +#ifdef __MRC__ + +#define MAC_STATIC + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +void Sys_PumpEvents( void ); + +#undef QDECL +#define QDECL __cdecl + +#define _alloca alloca +#endif + +//======================= LINUX DEFINES ================================= + +// the mac compiler can't handle >32k of locals, so we +// just waste space and make big arrays static... +#if GDEF_OS_LINUX + +#define MAC_STATIC + +#if GDEF_ARCH_BITS_32 +#define CPUSTRING "linux-i386" +#elif defined __axp__ +#define CPUSTRING "linux-alpha" +#else +#define CPUSTRING "linux-other" +#endif + +#define PATH_SEP '/' + +#endif + +//============================================================= + +typedef enum {qfalse, qtrue} qboolean; + +typedef unsigned char byte; + +#define EQUAL_EPSILON 0.001 + +typedef int qhandle_t; +typedef int sfxHandle_t; +typedef int fileHandle_t; +typedef int clipHandle_t; + +typedef enum { + INVALID_JOINT = -1 +} jointHandle_t; + +#ifndef NULL +#define NULL ( (void *)0 ) +#endif + +#define MAX_QINT 0x7fffffff +#define MIN_QINT ( -MAX_QINT - 1 ) + +#if !defined(__cplusplus) && !defined(max) +#define max( x, y ) ( ( ( x ) > ( y ) ) ? ( x ) : ( y ) ) +#define min( x, y ) ( ( ( x ) < ( y ) ) ? ( x ) : ( y ) ) +#endif + +#ifndef sign +#define sign( f ) ( ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 0 ) ) +#endif + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +// the game guarantees that no string from the network will ever +// exceed MAX_STRING_CHARS +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 256 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 1024 // max length of an individual token + +#define MAX_INFO_STRING 1024 +#define MAX_INFO_KEY 1024 +#define MAX_INFO_VALUE 1024 + + +#define MAX_QPATH 64 // max length of a quake game pathname +#ifdef PATH_MAX +#define MAX_OSPATH PATH_MAX // max length of a filesystem pathname +#else +#define MAX_OSPATH 128 // max length of a filesystem pathname +#endif + +#define MAX_NAME_LENGTH 32 // max length of a client name + +// paramters for command buffer stuffing +typedef enum { + EXEC_NOW, // don't return until completed, a VM should NEVER use this, + // because some commands might cause the VM to be unloaded... + EXEC_INSERT, // insert at current position, but don't run yet + EXEC_APPEND // add to end of the command buffer (normal case) +} cbufExec_t; + + +// +// these aren't needed by any of the VMs. put in another header? +// +#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility + +#undef ERR_FATAL // malloc.h on unix + +// parameters to the main Error routine +typedef enum { + ERR_NONE, + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_DISCONNECT, // don't kill server + ERR_NEED_CD // pop up the need-cd dialog +} errorParm_t; + + +// font rendering values used by ui and cgame + +#define PROP_GAP_WIDTH 3 +#define PROP_SPACE_WIDTH 8 +#define PROP_HEIGHT 27 +#define PROP_SMALL_SIZE_SCALE 0.75 + +#define BLINK_DIVISOR 200 +#define PULSE_DIVISOR 75 + +#define UI_LEFT 0x00000000 // default +#define UI_CENTER 0x00000001 +#define UI_RIGHT 0x00000002 +#define UI_FORMATMASK 0x00000007 +#define UI_SMALLFONT 0x00000010 +#define UI_BIGFONT 0x00000020 // default +#define UI_GIANTFONT 0x00000040 +#define UI_DROPSHADOW 0x00000800 +#define UI_BLINK 0x00001000 +#define UI_INVERSE 0x00002000 +#define UI_PULSE 0x00004000 + + +/* + ============================================================== + + MATHLIB + + ============================================================== + */ +#ifdef __cplusplus // so we can include this in C code +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define Q_PI 3.14159265358979323846 +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#include "math_vector.h" +#include "math_angles.h" +#include "math_matrix.h" +#include "math_quaternion.h" + +class idVec3; // for defining vectors +typedef idVec3 &vec3_p; // for passing vectors as function arguments +typedef const idVec3 &vec3_c; // for passing vectors as const function arguments + +class angles_t; // for defining angle vectors +typedef angles_t &angles_p; // for passing angles as function arguments +typedef const angles_t &angles_c; // for passing angles as const function arguments + +class mat3_t; // for defining matrices +typedef mat3_t &mat3_p; // for passing matrices as function arguments +typedef const mat3_t &mat3_c; // for passing matrices as const function arguments + + + +#define NUMVERTEXNORMALS 162 +extern idVec3 bytedirs[NUMVERTEXNORMALS]; + +// all drawing is done to a 640*480 virtual screen size +// and will be automatically scaled to the real resolution +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define TINYCHAR_WIDTH ( SMALLCHAR_WIDTH ) +#define TINYCHAR_HEIGHT ( SMALLCHAR_HEIGHT / 2 ) + +#define SMALLCHAR_WIDTH 8 +#define SMALLCHAR_HEIGHT 16 + +#define BIGCHAR_WIDTH 16 +#define BIGCHAR_HEIGHT 16 + +#define GIANTCHAR_WIDTH 32 +#define GIANTCHAR_HEIGHT 48 + +extern idVec4 colorBlack; +extern idVec4 colorRed; +extern idVec4 colorGreen; +extern idVec4 colorBlue; +extern idVec4 colorYellow; +extern idVec4 colorMagenta; +extern idVec4 colorCyan; +extern idVec4 colorWhite; +extern idVec4 colorLtGrey; +extern idVec4 colorMdGrey; +extern idVec4 colorDkGrey; + +#define Q_COLOR_ESCAPE '^' +#define Q_IsColorString( p ) ( p && *( p ) == Q_COLOR_ESCAPE && *( ( p ) + 1 ) && *( ( p ) + 1 ) != Q_COLOR_ESCAPE ) + +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' +#define COLOR_YELLOW '3' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' +#define COLOR_MAGENTA '6' +#define COLOR_WHITE '7' +#define ColorIndex( c ) ( ( ( c ) - '0' ) & 7 ) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +extern idVec4 g_color_table[8]; + +#define MAKERGB( v, r, g, b ) v[0] = r; v[1] = g; v[2] = b +#define MAKERGBA( v, r, g, b, a ) v[0] = r; v[1] = g; v[2] = b; v[3] = a + +#define DEG2RAD( a ) ( ( ( a ) * M_PI ) / 180.0F ) +#define RAD2DEG( a ) ( ( ( a ) * 180.0f ) / M_PI ) + +struct cplane_s; + +extern idVec3 vec3_origin; +extern idVec4 vec4_origin; +extern mat3_t axisDefault; + +#define nanmask ( 255 << 23 ) + +#define IS_NAN( x ) ( ( ( *(int *)&x ) & nanmask ) == nanmask ) + +float Q_fabs( float f ); +float Q_rsqrt( float f ); // reciprocal square root + +#define SQRTFAST( x ) ( 1.0f / Q_rsqrt( x ) ) + +signed char ClampChar( int i ); +signed short ClampShort( int i ); + +// this isn't a real cheap function to call! +int DirToByte( const idVec3 &dir ); +void ByteToDir( int b, vec3_p dir ); + +#define DotProduct( a,b ) ( ( a )[0] * ( b )[0] + ( a )[1] * ( b )[1] + ( a )[2] * ( b )[2] ) +#define VectorSubtract( a,b,c ) ( ( c )[0] = ( a )[0] - ( b )[0],( c )[1] = ( a )[1] - ( b )[1],( c )[2] = ( a )[2] - ( b )[2] ) +#define VectorAdd( a,b,c ) ( ( c )[0] = ( a )[0] + ( b )[0],( c )[1] = ( a )[1] + ( b )[1],( c )[2] = ( a )[2] + ( b )[2] ) +#define VectorCopy( a,b ) ( ( b )[0] = ( a )[0],( b )[1] = ( a )[1],( b )[2] = ( a )[2] ) +//#define VectorCopy(a,b) ((b).x=(a).x,(b).y=(a).y,(b).z=(a).z]) + +#define VectorScale( v, s, o ) ( ( o )[0] = ( v )[0] * ( s ),( o )[1] = ( v )[1] * ( s ),( o )[2] = ( v )[2] * ( s ) ) +#define VectorMA( 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 ) ) +#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 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 VectorClear( a ) ( ( a )[0] = ( a )[1] = ( a )[2] = 0 ) +#define VectorNegate( a,b ) ( ( b )[0] = -( a )[0],( b )[1] = -( a )[1],( b )[2] = -( a )[2] ) +#define VectorSet( v, x, y, z ) ( ( v )[0] = ( x ), ( v )[1] = ( y ), ( v )[2] = ( z ) ) +#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]; } + +float NormalizeColor( vec3_c in, vec3_p out ); + +int VectorCompare( vec3_c v1, vec3_c v2 ); +float VectorLength( vec3_c v ); +float Distance( vec3_c p1, vec3_c p2 ); +float DistanceSquared( vec3_c p1, vec3_c p2 ); +float VectorNormalize( vec3_p v ); // returns vector length +void VectorNormalizeFast( vec3_p v ); // does NOT return vector length, uses rsqrt approximation +float VectorNormalize2( vec3_c v, vec3_p out ); +void VectorInverse( vec3_p v ); +void VectorRotate( vec3_c in, mat3_c matrix, vec3_p out ); +void VectorPolar( vec3_p v, float radius, float theta, float phi ); +void VectorSnap( vec3_p v ); +void Vector53Copy( const idVec5_t &in, vec3_p out ); +void Vector5Scale( const idVec5_t &v, float scale, idVec5_t &out ); +void Vector5Add( const idVec5_t &va, const idVec5_t &vb, idVec5_t &out ); +void VectorRotate3( vec3_c vIn, vec3_c vRotation, vec3_p out ); +void VectorRotate3Origin( vec3_c vIn, vec3_c vRotation, vec3_c vOrigin, vec3_p out ); + + +int Q_log2( int val ); + +int Q_rand( int *seed ); +float Q_random( int *seed ); +float Q_crandom( int *seed ); + +#define random() ( ( rand() & 0x7fff ) / ( (float)0x7fff ) ) +#define crandom() ( 2.0 * ( random() - 0.5 ) ) + +float Q_rint( float in ); + +void vectoangles( vec3_c value1, angles_p angles ); +void AnglesToAxis( angles_c angles, mat3_p axis ); + +void AxisCopy( mat3_c in, mat3_p out ); +qboolean AxisRotated( mat3_c in ); // assumes a non-degenerate axis + +int SignbitsForNormal( vec3_c normal ); +int BoxOnPlaneSide( const Bounds &b, struct cplane_s *p ); + +float AngleMod( float a ); +float LerpAngle( float from, float to, float frac ); +float AngleSubtract( float a1, float a2 ); +void AnglesSubtract( angles_c v1, angles_c v2, angles_p v3 ); + +float AngleNormalize360( float angle ); +float AngleNormalize180( float angle ); +float AngleDelta( float angle1, float angle2 ); + +qboolean PlaneFromPoints( idVec4 &plane, vec3_c a, vec3_c b, vec3_c c ); +void ProjectPointOnPlane( vec3_p dst, vec3_c p, vec3_c normal ); +void RotatePointAroundVector( vec3_p dst, vec3_c dir, vec3_c point, float degrees ); +void RotateAroundDirection( mat3_p axis, float yaw ); +void MakeNormalVectors( vec3_c forward, vec3_p right, vec3_p up ); +// perpendicular vector could be replaced by this + +int PlaneTypeForNormal( vec3_c normal ); + +void MatrixMultiply( mat3_c in1, mat3_c in2, mat3_p out ); +void MatrixInverseMultiply( mat3_c in1, mat3_c in2, mat3_p out ); // in2 is transposed during multiply +void MatrixTransformVector( vec3_c in, mat3_c matrix, vec3_p out ); +void MatrixProjectVector( vec3_c in, mat3_c matrix, vec3_p out ); // Places the vector into a new coordinate system. +void AngleVectors( angles_c angles, vec3_p forward, vec3_p right, vec3_p up ); +void PerpendicularVector( vec3_p dst, vec3_c src ); + +float TriangleArea( vec3_c a, vec3_c b, vec3_c c ); +#endif // __cplusplus + +//============================================= + +float Com_Clamp( float min, float max, float value ); + +#define FILE_HASH_SIZE 1024 +int Com_HashString( const char *fname ); + +char *Com_SkipPath( char *pathname ); + +// it is ok for out == in +void Com_StripExtension( const char *in, char *out ); + +// "extension" should include the dot: ".map" +void Com_DefaultExtension( char *path, int maxSize, const char *extension ); + +int Com_ParseInfos( const char *buf, int max, char infos[][MAX_INFO_STRING] ); + +/* + ===================================================================================== + + SCRIPT PARSING + + ===================================================================================== + */ + +// this just controls the comment printing, it doesn't actually load a file +void Com_BeginParseSession( const char *filename ); +void Com_EndParseSession( void ); + +int Com_GetCurrentParseLine( void ); + +// Will never return NULL, just empty strings. +// An empty string will only be returned at end of file. +// ParseOnLine will return empty if there isn't another token on this line + +// this funny typedef just means a moving pointer into a const char * buffer +const char *Com_Parse( const char *( *data_p ) ); +const char *Com_ParseOnLine( const char *( *data_p ) ); +const char *Com_ParseRestOfLine( const char *( *data_p ) ); + +void Com_UngetToken( void ); + +#ifdef __cplusplus +void Com_MatchToken( const char *( *buf_p ), const char *match, qboolean warning = qfalse ); +#else +void Com_MatchToken( const char *( *buf_p ), const char *match, qboolean warning ); +#endif + +void Com_ScriptError( const char *msg, ... ); +void Com_ScriptWarning( const char *msg, ... ); + +void Com_SkipBracedSection( const char *( *program ) ); +void Com_SkipRestOfLine( const char *( *data ) ); + +float Com_ParseFloat( const char *( *buf_p ) ); +int Com_ParseInt( const char *( *buf_p ) ); + +void Com_Parse1DMatrix( const char *( *buf_p ), int x, float *m ); +void Com_Parse2DMatrix( const char *( *buf_p ), int y, int x, float *m ); +void Com_Parse3DMatrix( const char *( *buf_p ), int z, int y, int x, float *m ); + +//===================================================================================== +#ifdef __cplusplus +extern "C" { +#endif + +void QDECL Com_sprintf( char *dest, std::size_t size, const char *fmt, ... ); + + +// mode parm for FS_FOpenFile +typedef enum { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +} fsMode_t; + +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +//============================================= + +int Q_isprint( int c ); +int Q_islower( int c ); +int Q_isupper( int c ); +int Q_isalpha( int c ); + +// portable case insensitive compare +int Q_stricmp( const char *s1, const char *s2 ); +int Q_strncmp( const char *s1, const char *s2, int n ); +int Q_stricmpn( const char *s1, const char *s2, int n ); +char *Q_strlwr( char *s1 ); +char *Q_strupr( char *s1 ); +char *Q_strrchr( const char* string, int c ); + +// buffer size safe library replacements +void Q_strncpyz( char *dest, const char *src, std::size_t destsize ); +void Q_strcat( char *dest, std::size_t size, const char *src ); + +// strlen that discounts Quake color sequences +int Q_PrintStrlen( const char *string ); +// removes color sequences from string +char *Q_CleanStr( char *string ); + +int Com_Filter( const char *filter, const char *name, int casesensitive ); +const char *Com_StringContains( const char *str1, const char *str2, int casesensitive ); + + +//============================================= + +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +float BigFloat( float l ); +float LittleFloat( float l ); + +void Swap_Init( void ); +char *QDECL va( const char *format, ... ); + +#ifdef __cplusplus +} +#endif + + +//============================================= +#ifdef __cplusplus +// +// mapfile parsing +// +typedef struct ePair_s { + char *key; + char *value; +} ePair_t; + +typedef struct mapSide_s { + char material[MAX_QPATH]; + idVec4 plane; + idVec4 textureVectors[2]; +} mapSide_t; + +typedef struct { + int numSides; + mapSide_t **sides; +} mapBrush_t; + +typedef struct { + idVec3 xyz; + float st[2]; +} patchVertex_t; + +typedef struct { + char material[MAX_QPATH]; + int width, height; + patchVertex_t *patchVerts; +} mapPatch_t; + +typedef struct { + char modelName[MAX_QPATH]; + float matrix[16]; +} mapModel_t; + +typedef struct mapPrimitive_s { + int numEpairs; + ePair_t **ePairs; + + // only one of these will be non-NULL + mapBrush_t *brush; + mapPatch_t *patch; + mapModel_t *model; +} mapPrimitive_t; + +typedef struct mapEntity_s { + int numPrimitives; + mapPrimitive_t **primitives; + + int numEpairs; + ePair_t **ePairs; +} mapEntity_t; + +typedef struct { + int numEntities; + mapEntity_t **entities; +} mapFile_t; + + +// the order of entities, brushes, and sides will be maintained, the +// lists won't be swapped on each load or save +mapFile_t *ParseMapFile( const char *text ); +void FreeMapFile( mapFile_t *mapFile ); +void WriteMapFile( const mapFile_t *mapFile, FILE *f ); + +// key names are case-insensitive +const char *ValueForMapEntityKey( const mapEntity_t *ent, const char *key ); +float FloatForMapEntityKey( const mapEntity_t *ent, const char *key ); +qboolean GetVectorForMapEntityKey( const mapEntity_t *ent, const char *key, idVec3 &vec ); + +typedef struct { + idVec3 xyz; + idVec2 st; + idVec3 normal; + idVec3 tangents[2]; + byte smoothing[4]; // colors for silhouette smoothing +} drawVert_t; + +typedef struct { + int width, height; + drawVert_t *verts; +} drawVertMesh_t; + +// Tesselate a map patch into smoothed, drawable vertexes +// MaxError of around 4 is reasonable +drawVertMesh_t *SubdivideMapPatch( const mapPatch_t *patch, float maxError ); +#endif // __cplusplus + +//========================================= + +#ifdef __cplusplus +extern "C" { +#endif + +void QDECL Com_Error( int level, const char *error, ... ); +void QDECL Com_Printf( const char *msg, ... ); +void QDECL Com_DPrintf( const char *msg, ... ); + +#ifdef __cplusplus +} +#endif + + +typedef struct { + qboolean frameMemory; + int currentElements; + int maxElements; // will reallocate and move when exceeded + void **elements; +} growList_t; + +// you don't need to init the growlist if you don't mind it growing and moving +// the list as it expands +void Com_InitGrowList( growList_t *list, int maxElements ); +int Com_AddToGrowList( growList_t *list, void *data ); +void *Com_GrowListElement( const growList_t *list, int index ); +int Com_IndexForGrowListElement( const growList_t *list, const void *element ); + + +// +// key / value info strings +// +const char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemoveKey( char *s, const char *key ); +void Info_SetValueForKey( char *s, const char *key, const char *value ); +qboolean Info_Validate( const char *s ); +void Info_NextPair( const char *( *s ), char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ); + +// get cvar defs, collision defs, etc +//#include "../shared/interface.h" + +// get key code numbers for events +//#include "../shared/keycodes.h" + +#ifdef __cplusplus +// get the polygon winding functions +//#include "../shared/windings.h" + +// get the flags class +//#include "../shared/idflags.h" +#endif // __cplusplus + +#endif // __Q_SHARED_H diff --git a/libs/splines/splines.cpp b/libs/splines/splines.cpp new file mode 100644 index 0000000..e35d82b --- /dev/null +++ b/libs/splines/splines.cpp @@ -0,0 +1,1449 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "q_shared.h" +#include "splines.h" + +extern "C" { +int FS_Write( const void *buffer, int len, fileHandle_t h ); +int FS_ReadFile( const char *qpath, void **buffer ); +void FS_FreeFile( void *buffer ); +fileHandle_t FS_FOpenFileWrite( const char *filename ); +void FS_FCloseFile( fileHandle_t f ); +void Cbuf_AddText( const char *text ); +void Cbuf_Execute( void ); +} + +float Q_fabs( float f ) { + int tmp = *( int * ) &f; + tmp &= 0x7FFFFFFF; + return *( float * ) &tmp; +} + +// (SA) making a list of cameras so I can use +// the splines as targets for other things. +// Certainly better ways to do this, but this lets +// me get underway quickly with ents that need spline +// targets. +const int MAX_CAMERAS = 64; + +idCameraDef camera[MAX_CAMERAS]; + +extern "C" { +qboolean loadCamera( int camNum, const char *name ) { + if ( camNum < 0 || camNum >= MAX_CAMERAS ) { + return qfalse; + } + camera[camNum].clear(); + return (qboolean)camera[camNum].load( name ); +} + +qboolean getCameraInfo( int camNum, int time, float *origin, float *angles, float *fov ) { + idVec3 dir, org; + if ( camNum < 0 || camNum >= MAX_CAMERAS ) { + return qfalse; + } + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + if ( camera[camNum].getCameraInfo( time, org, dir, fov ) ) { + origin[0] = org[0]; + origin[1] = org[1]; + origin[2] = org[2]; + angles[1] = atan2( dir[1], dir[0] ) * 180 / 3.14159; + angles[0] = asin( dir[2] ) * 180 / 3.14159; + return qtrue; + } + return qfalse; +} + +void startCamera( int camNum, int time ) { + if ( camNum < 0 || camNum >= MAX_CAMERAS ) { + return; + } + camera[camNum].startCamera( time ); +} + +} + + +//#include "../shared/windings.h" +//#include "../qcommon/qcommon.h" +//#include "../sys/sys_public.h" +//#include "../game/game_entity.h" + +idCameraDef splineList; +idCameraDef *g_splineList = &splineList; + +idVec3 idSplineList::zero( 0,0,0 ); + +void glLabeledPoint( idVec3 &color, idVec3 &point, float size, const char *label ) { + glColor3fv( color ); + glPointSize( size ); + glBegin( GL_POINTS ); + glVertex3fv( point ); + glEnd(); + idVec3 v = point; + v.x += 1; + v.y += 1; + v.z += 1; + glRasterPos3fv( v ); + glCallLists( strlen( label ), GL_UNSIGNED_BYTE, label ); +} + + +void glBox( idVec3 &color, idVec3 &point, float size ) { + idVec3 mins( point ); + idVec3 maxs( point ); + mins[0] -= size; + mins[1] += size; + mins[2] -= size; + maxs[0] += size; + maxs[1] -= size; + maxs[2] += size; + glColor3fv( color ); + glBegin( GL_LINE_LOOP ); + glVertex3f( mins[0],mins[1],mins[2] ); + glVertex3f( maxs[0],mins[1],mins[2] ); + glVertex3f( maxs[0],maxs[1],mins[2] ); + glVertex3f( mins[0],maxs[1],mins[2] ); + glEnd(); + glBegin( GL_LINE_LOOP ); + glVertex3f( mins[0],mins[1],maxs[2] ); + glVertex3f( maxs[0],mins[1],maxs[2] ); + glVertex3f( maxs[0],maxs[1],maxs[2] ); + glVertex3f( mins[0],maxs[1],maxs[2] ); + glEnd(); + + glBegin( GL_LINES ); + glVertex3f( mins[0],mins[1],mins[2] ); + glVertex3f( mins[0],mins[1],maxs[2] ); + glVertex3f( mins[0],maxs[1],maxs[2] ); + glVertex3f( mins[0],maxs[1],mins[2] ); + glVertex3f( maxs[0],mins[1],mins[2] ); + glVertex3f( maxs[0],mins[1],maxs[2] ); + glVertex3f( maxs[0],maxs[1],maxs[2] ); + glVertex3f( maxs[0],maxs[1],mins[2] ); + glEnd(); + +} + +void splineTest() { + //g_splineList->load("p:/doom/base/maps/test_base1.camera"); +} + +void splineDraw() { + //g_splineList->addToRenderer(); +} + + +//extern void D_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end ); + +void debugLine( idVec3 &color, float x, float y, float z, float x2, float y2, float z2 ) { + idVec3 from( x, y, z ); + idVec3 to( x2, y2, z2 ); + //D_DebugLine(color, from, to); +} + +void idSplineList::addToRenderer() { + + if ( controlPoints.Num() == 0 ) { + return; + } + + idVec3 mins, maxs; + idVec3 yellow( 1.0, 1.0, 0 ); + idVec3 white( 1.0, 1.0, 1.0 ); + int i; + + for ( i = 0; i < controlPoints.Num(); i++ ) { + VectorCopy( *controlPoints[i], mins ); + VectorCopy( mins, maxs ); + mins[0] -= 8; + mins[1] += 8; + mins[2] -= 8; + maxs[0] += 8; + maxs[1] -= 8; + maxs[2] += 8; + debugLine( yellow, mins[0], mins[1], mins[2], maxs[0], mins[1], mins[2] ); + debugLine( yellow, maxs[0], mins[1], mins[2], maxs[0], maxs[1], mins[2] ); + debugLine( yellow, maxs[0], maxs[1], mins[2], mins[0], maxs[1], mins[2] ); + debugLine( yellow, mins[0], maxs[1], mins[2], mins[0], mins[1], mins[2] ); + + debugLine( yellow, mins[0], mins[1], maxs[2], maxs[0], mins[1], maxs[2] ); + debugLine( yellow, maxs[0], mins[1], maxs[2], maxs[0], maxs[1], maxs[2] ); + debugLine( yellow, maxs[0], maxs[1], maxs[2], mins[0], maxs[1], maxs[2] ); + debugLine( yellow, mins[0], maxs[1], maxs[2], mins[0], mins[1], maxs[2] ); + + } + + int step = 0; + idVec3 step1; + for ( i = 3; i < controlPoints.Num(); i++ ) { + for ( float tension = 0.0f; tension < 1.001f; tension += 0.1f ) { + float x = 0; + float y = 0; + float z = 0; + for ( int j = 0; j < 4; j++ ) { + x += controlPoints[i - ( 3 - j )]->x * calcSpline( j, tension ); + y += controlPoints[i - ( 3 - j )]->y * calcSpline( j, tension ); + z += controlPoints[i - ( 3 - j )]->z * calcSpline( j, tension ); + } + if ( step == 0 ) { + step1[0] = x; + step1[1] = y; + step1[2] = z; + step = 1; + } + else { + debugLine( white, step1[0], step1[1], step1[2], x, y, z ); + step = 0; + } + + } + } +} + +void idSplineList::buildSpline() { + //int start = Sys_Milliseconds(); + clearSpline(); + for ( int i = 3; i < controlPoints.Num(); i++ ) { + for ( float tension = 0.0f; tension < 1.001f; tension += granularity ) { + float x = 0; + float y = 0; + float z = 0; + for ( int j = 0; j < 4; j++ ) { + x += controlPoints[i - ( 3 - j )]->x * calcSpline( j, tension ); + y += controlPoints[i - ( 3 - j )]->y * calcSpline( j, tension ); + z += controlPoints[i - ( 3 - j )]->z * calcSpline( j, tension ); + } + splinePoints.Append( new idVec3( x, y, z ) ); + } + } + dirty = false; + //Com_Printf("Spline build took %f seconds\n", (float)(Sys_Milliseconds() - start) / 1000); +} + + +void idSplineList::draw( bool editMode ) { + int i; + idVec4 yellow( 1, 1, 0, 1 ); + + if ( controlPoints.Num() == 0 ) { + return; + } + + if ( dirty ) { + buildSpline(); + } + + + glColor3fv( controlColor ); + glPointSize( 5 ); + + glBegin( GL_POINTS ); + for ( i = 0; i < controlPoints.Num(); i++ ) { + glVertex3fv( *controlPoints[i] ); + } + glEnd(); + + if ( editMode ) { + for ( i = 0; i < controlPoints.Num(); i++ ) { + glBox( activeColor, *controlPoints[i], 4 ); + } + } + + //Draw the curve + glColor3fv( pathColor ); + glBegin( GL_LINE_STRIP ); + int count = splinePoints.Num(); + for ( i = 0; i < count; i++ ) { + glVertex3fv( *splinePoints[i] ); + } + glEnd(); + + if ( editMode ) { + glColor3fv( segmentColor ); + glPointSize( 3 ); + glBegin( GL_POINTS ); + for ( i = 0; i < count; i++ ) { + glVertex3fv( *splinePoints[i] ); + } + glEnd(); + } + if ( count > 0 ) { + //assert(activeSegment >=0 && activeSegment < count); + if ( activeSegment >= 0 && activeSegment < count ) { + glBox( activeColor, *splinePoints[activeSegment], 6 ); + glBox( yellow, *splinePoints[activeSegment], 8 ); + } + } + +} + +float idSplineList::totalDistance() { + + // FIXME: save dist and return + // + if ( controlPoints.Num() == 0 ) { + return 0.0; + } + + if ( dirty ) { + buildSpline(); + } + + float dist = 0.0; + idVec3 temp; + int count = splinePoints.Num(); + for ( int i = 1; i < count; i++ ) { + temp = *splinePoints[i - 1]; + temp -= *splinePoints[i]; + dist += temp.Length(); + } + return dist; +} + +void idSplineList::initPosition( long bt, long totalTime ) { + + if ( dirty ) { + buildSpline(); + } + + if ( splinePoints.Num() == 0 ) { + return; + } + + baseTime = bt; + time = totalTime; + + // calc distance to travel ( this will soon be broken into time segments ) + splineTime.Clear(); + splineTime.Append( bt ); + double dist = totalDistance(); + double distSoFar = 0.0; + idVec3 temp; + int count = splinePoints.Num(); + //for(int i = 2; i < count - 1; i++) { + for ( int i = 1; i < count; i++ ) { + temp = *splinePoints[i - 1]; + temp -= *splinePoints[i]; + distSoFar += temp.Length(); + double percent = distSoFar / dist; + percent *= totalTime; + splineTime.Append( percent + bt ); + } + assert( splineTime.Num() == splinePoints.Num() ); + activeSegment = 0; +} + + + +float idSplineList::calcSpline( int step, float tension ) { + switch ( step ) { + case 0: return ( pow( 1 - tension, 3 ) ) / 6; + case 1: return ( 3 * pow( tension, 3 ) - 6 * pow( tension, 2 ) + 4 ) / 6; + case 2: return ( -3 * pow( tension, 3 ) + 3 * pow( tension, 2 ) + 3 * tension + 1 ) / 6; + case 3: return pow( tension, 3 ) / 6; + } + return 0.0; +} + + + +void idSplineList::updateSelection( const idVec3 &move ) { + if ( selected ) { + dirty = true; + VectorAdd( *selected, move, *selected ); + } +} + + +void idSplineList::setSelectedPoint( idVec3 *p ) { + if ( p ) { + p->Snap(); + for ( int i = 0; i < controlPoints.Num(); i++ ) { + if ( *p == *controlPoints[i] ) { + selected = controlPoints[i]; + } + } + } + else { + selected = NULL; + } +} + +const idVec3 *idSplineList::getPosition( long t ) { + static idVec3 interpolatedPos; + + int count = splineTime.Num(); + if ( count == 0 ) { + return &zero; + } + +// Com_Printf("Time: %d\n", t); + assert( splineTime.Num() == splinePoints.Num() ); + + while ( activeSegment < count ) { + if ( splineTime[activeSegment] >= t ) { + if ( activeSegment > 0 && activeSegment < count - 1 ) { + double timeHi = splineTime[activeSegment + 1]; + double timeLo = splineTime[activeSegment - 1]; + double percent = ( timeHi - t ) / ( timeHi - timeLo ); + // pick two bounding points + idVec3 v1 = *splinePoints[activeSegment - 1]; + idVec3 v2 = *splinePoints[activeSegment + 1]; + v2 *= ( 1.0 - percent ); + v1 *= percent; + v2 += v1; + interpolatedPos = v2; + return &interpolatedPos; + } + return splinePoints[activeSegment]; + } + else { + activeSegment++; + } + } + return splinePoints[count - 1]; +} + +void idSplineList::parse( const char *( *text ) ) { + const char *token; + //Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !Q_stricmp( token, "}" ) ) { + break; + } + + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !Q_stricmp( token, "(" ) || !Q_stricmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "granularity" ) == 0 ) { + granularity = atof( token ); + } + else if ( Q_stricmp( key.c_str(), "name" ) == 0 ) { + name = token; + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !Q_stricmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + // read the control point + idVec3 point; + Com_Parse1DMatrix( text, 3, point ); + addPoint( point.x, point.y, point.z ); + } while ( 1 ); + + //Com_UngetToken(); + //Com_MatchToken( text, "}" ); + dirty = true; +} + +void idSplineList::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + //s = va("\t\tname %s\n", name.c_str()); + //FS_Write(s.c_str(), s.length(), file); + s = va( "\t\t\tgranularity %f\n", granularity ); + FS_Write( s.c_str(), s.length(), file ); + int count = controlPoints.Num(); + for ( int i = 0; i < count; i++ ) { + s = va( "\t\t\t( %f %f %f )\n", controlPoints[i]->x, controlPoints[i]->y, controlPoints[i]->z ); + FS_Write( s.c_str(), s.length(), file ); + } + s = "\t\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + + +void idCameraDef::getActiveSegmentInfo( int segment, idVec3 &origin, idVec3 &direction, float *fov ) { +#if 0 + if ( !cameraSpline.validTime() ) { + buildCamera(); + } + double d = (double)segment / numSegments(); + getCameraInfo( d * totalTime * 1000, origin, direction, fov ); +#endif +/* + if (!cameraSpline.validTime()) { + buildCamera(); + } + origin = *cameraSpline.getSegmentPoint(segment); + + + idVec3 temp; + + int numTargets = getTargetSpline()->controlPoints.Num(); + int count = cameraSpline.splineTime.Num(); + if (numTargets == 0) { + // follow the path + if (cameraSpline.getActiveSegment() < count - 1) { + temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1]; + } + } else if (numTargets == 1) { + temp = *getTargetSpline()->controlPoints[0]; + } else { + temp = *getTargetSpline()->getSegmentPoint(segment); + } + + temp -= origin; + temp.Normalize(); + direction = temp; + */ +} + +bool idCameraDef::getCameraInfo( long time, idVec3 &origin, idVec3 &direction, float *fv ) { + + char buff[1024]; + + if ( ( time - startTime ) / 1000 > totalTime ) { + return false; + } + + + for ( int i = 0; i < events.Num(); i++ ) { + if ( time >= startTime + events[i]->getTime() && !events[i]->getTriggered() ) { + events[i]->setTriggered( true ); + if ( events[i]->getType() == idCameraEvent::EVENT_TARGET ) { + setActiveTargetByName( events[i]->getParam() ); + getActiveTarget()->start( startTime + events[i]->getTime() ); + //Com_Printf("Triggered event switch to target: %s\n",events[i]->getParam()); + } + else if ( events[i]->getType() == idCameraEvent::EVENT_TRIGGER ) { + //idEntity *ent = NULL; + //ent = level.FindTarget( ent, events[i]->getParam()); + //if (ent) { + // ent->signal( SIG_TRIGGER ); + // ent->ProcessEvent( &EV_Activate, world ); + //} + } + else if ( events[i]->getType() == idCameraEvent::EVENT_FOV ) { + memset( buff, 0, sizeof( buff ) ); + strcpy( buff, events[i]->getParam() ); + const char *param1 = strtok( buff, " \t,\0" ); + const char *param2 = strtok( NULL, " \t,\0" ); + float len = ( param2 ) ? atof( param2 ) : 0; + float newfov = ( param1 ) ? atof( param1 ) : 90; + fov.reset( fov.getFOV( time ), newfov, time, len ); + //*fv = fov = atof(events[i]->getParam()); + } + else if ( events[i]->getType() == idCameraEvent::EVENT_FADEIN ) { + float time = atof( events[i]->getParam() ); + Cbuf_AddText( va( "fade 0 0 0 0 %f", time ) ); + Cbuf_Execute(); + } + else if ( events[i]->getType() == idCameraEvent::EVENT_FADEOUT ) { + float time = atof( events[i]->getParam() ); + Cbuf_AddText( va( "fade 0 0 0 255 %f", time ) ); + Cbuf_Execute(); + } + else if ( events[i]->getType() == idCameraEvent::EVENT_CAMERA ) { + memset( buff, 0, sizeof( buff ) ); + strcpy( buff, events[i]->getParam() ); + const char *param1 = strtok( buff, " \t,\0" ); + const char *param2 = strtok( NULL, " \t,\0" ); + + if ( param2 ) { + loadCamera( atoi( param1 ), va( "cameras/%s.camera", param2 ) ); + startCamera( time ); + } + else { + loadCamera( 0, va( "cameras/%s.camera", events[i]->getParam() ) ); + startCamera( time ); + } + return true; + } + else if ( events[i]->getType() == idCameraEvent::EVENT_STOP ) { + return false; + } + } + } + + origin = *cameraPosition->getPosition( time ); + + *fv = fov.getFOV( time ); + + idVec3 temp = origin; + + int numTargets = targetPositions.Num(); + if ( numTargets == 0 ) { +/* + // follow the path + if (cameraSpline.getActiveSegment() < count - 1) { + temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1]; + if (temp == origin) { + int index = cameraSpline.getActiveSegment() + 2; + while (temp == origin && index < count - 1) { + temp = *cameraSpline.splinePoints[index++]; + } + } + } + */ + } + else { + if ( getActiveTarget()->numPoints() > 0 ) { + temp = *getActiveTarget()->getPosition( time ); + } + } + + temp -= origin; + temp.Normalize(); + direction = temp; + + return true; +} + +bool idCameraDef::waitEvent( int index ) { + //for (int i = 0; i < events.Num(); i++) { + // if (events[i]->getSegment() == index && events[i]->getType() == idCameraEvent::EVENT_WAIT) { + // return true; + // } + //} + return false; +} + + +const int NUM_CCELERATION_SEGS = 10; +const int CELL_AMT = 5; + +void idCameraDef::buildCamera() { + int i; + idList waits; + idList targets; + + totalTime = baseTime; + cameraPosition->setTime( (long)totalTime * 1000 ); + // we have a base time layout for the path and the target path + // now we need to layer on any wait or speed changes + for ( i = 0; i < events.Num(); i++ ) { + events[i]->setTriggered( false ); + switch ( events[i]->getType() ) { + default: break; + case idCameraEvent::EVENT_TARGET: { + targets.Append( i ); + break; + } + case idCameraEvent::EVENT_FEATHER: { + long startTime = 0; + float speed = 0; + long loopTime = 10; + float stepGoal = cameraPosition->getBaseVelocity() / ( 1000 / loopTime ); + while ( startTime <= 1000 ) { + cameraPosition->addVelocity( startTime, loopTime, speed ); + speed += stepGoal; + if ( speed > cameraPosition->getBaseVelocity() ) { + speed = cameraPosition->getBaseVelocity(); + } + startTime += loopTime; + } + + startTime = (long)( totalTime * 1000 - 1000 ); + long endTime = startTime + 1000; + speed = cameraPosition->getBaseVelocity(); + while ( startTime < endTime ) { + speed -= stepGoal; + if ( speed < 0 ) { + speed = 0; + } + cameraPosition->addVelocity( startTime, loopTime, speed ); + startTime += loopTime; + } + break; + + } + case idCameraEvent::EVENT_WAIT: { + waits.Append( atof( events[i]->getParam() ) ); + + //FIXME: this is quite hacky for Wolf E3, accel and decel needs + // do be parameter based etc.. + long startTime = events[i]->getTime() - 1000; + if ( startTime < 0 ) { + startTime = 0; + } + float speed = cameraPosition->getBaseVelocity(); + long loopTime = 10; + float steps = speed / ( ( events[i]->getTime() - startTime ) / loopTime ); + while ( startTime <= events[i]->getTime() - loopTime ) { + cameraPosition->addVelocity( startTime, loopTime, speed ); + speed -= steps; + startTime += loopTime; + } + cameraPosition->addVelocity( events[i]->getTime(), (long)atof( events[i]->getParam() ) * 1000, 0 ); + + startTime = (long)( events[i]->getTime() + atof( events[i]->getParam() ) * 1000 ); + long endTime = startTime + 1000; + speed = 0; + while ( startTime <= endTime ) { + cameraPosition->addVelocity( startTime, loopTime, speed ); + speed += steps; + startTime += loopTime; + } + break; + } + case idCameraEvent::EVENT_TARGETWAIT: { + //targetWaits.Append(i); + break; + } + case idCameraEvent::EVENT_SPEED: { +/* + // take the average delay between up to the next five segments + float adjust = atof(events[i]->getParam()); + int index = events[i]->getSegment(); + total = 0; + count = 0; + + // get total amount of time over the remainder of the segment + for (j = index; j < cameraSpline.numSegments() - 1; j++) { + total += cameraSpline.getSegmentTime(j + 1) - cameraSpline.getSegmentTime(j); + count++; + } + + // multiply that by the adjustment + double newTotal = total * adjust; + // what is the difference.. + newTotal -= total; + totalTime += newTotal / 1000; + + // per segment difference + newTotal /= count; + int additive = newTotal; + + // now propogate that difference out to each segment + for (j = index; j < cameraSpline.numSegments(); j++) { + cameraSpline.addSegmentTime(j, additive); + additive += newTotal; + } + break; + */ + } + } + } + + + for ( i = 0; i < waits.Num(); i++ ) { + totalTime += waits[i]; + } + + // on a new target switch, we need to take time to this point ( since last target switch ) + // and allocate it across the active target, then reset time to this point + long timeSoFar = 0; + long total = (long)( totalTime * 1000 ); + for ( i = 0; i < targets.Num(); i++ ) { + long t; + if ( i < targets.Num() - 1 ) { + t = events[targets[i + 1]]->getTime(); + } + else { + t = total - timeSoFar; + } + // t is how much time to use for this target + setActiveTargetByName( events[targets[i]]->getParam() ); + getActiveTarget()->setTime( t ); + timeSoFar += t; + } + + +} + +void idCameraDef::startCamera( long t ) { + cameraPosition->clearVelocities(); + cameraPosition->start( t ); + buildCamera(); + fov.reset( 90, 90, t, 0 ); + //for (int i = 0; i < targetPositions.Num(); i++) { + // targetPositions[i]-> + //} + startTime = t; + cameraRunning = true; +} + + +void idCameraDef::parse( const char *( *text ) ) { + + const char *token; + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !Q_stricmp( token, "}" ) ) { + break; + } + + if ( Q_stricmp( token, "time" ) == 0 ) { + baseTime = Com_ParseFloat( text ); + } + else if ( Q_stricmp( token, "camera_fixed" ) == 0 ) { + cameraPosition = new idFixedPosition(); + cameraPosition->parse( text ); + } + else if ( Q_stricmp( token, "camera_interpolated" ) == 0 ) { + cameraPosition = new idInterpolatedPosition(); + cameraPosition->parse( text ); + } + else if ( Q_stricmp( token, "camera_spline" ) == 0 ) { + cameraPosition = new idSplinePosition(); + cameraPosition->parse( text ); + } + else if ( Q_stricmp( token, "target_fixed" ) == 0 ) { + idFixedPosition *pos = new idFixedPosition(); + pos->parse( text ); + targetPositions.Append( pos ); + } + else if ( Q_stricmp( token, "target_interpolated" ) == 0 ) { + idInterpolatedPosition *pos = new idInterpolatedPosition(); + pos->parse( text ); + targetPositions.Append( pos ); + } + else if ( Q_stricmp( token, "target_spline" ) == 0 ) { + idSplinePosition *pos = new idSplinePosition(); + pos->parse( text ); + targetPositions.Append( pos ); + } + else if ( Q_stricmp( token, "fov" ) == 0 ) { + fov.parse( text ); + } + else if ( Q_stricmp( token, "event" ) == 0 ) { + idCameraEvent *event = new idCameraEvent(); + event->parse( text ); + addEvent( event ); + } + + + } while ( 1 ); + + if ( !cameraPosition ) { + Com_Printf( "no camera position specified\n" ); + // prevent a crash later on + cameraPosition = new idFixedPosition(); + } + + Com_UngetToken(); + Com_MatchToken( text, "}" ); + +} + +bool idCameraDef::load( const char *filename ) { + char *buf; + const char *buf_p; + + FS_ReadFile( filename, (void **)&buf ); + if ( !buf ) { + return false; + } + + clear(); + Com_BeginParseSession( filename ); + buf_p = buf; + parse( &buf_p ); + Com_EndParseSession(); + FS_FreeFile( buf ); + + return true; +} + +void idCameraDef::save( const char *filename ) { + fileHandle_t file = FS_FOpenFileWrite( filename ); + if ( file ) { + int i; + idStr s = "cameraPathDef { \n"; + FS_Write( s.c_str(), s.length(), file ); + s = va( "\ttime %f\n", baseTime ); + FS_Write( s.c_str(), s.length(), file ); + + cameraPosition->write( file, va( "camera_%s",cameraPosition->typeStr() ) ); + + for ( i = 0; i < numTargets(); i++ ) { + targetPositions[i]->write( file, va( "target_%s", targetPositions[i]->typeStr() ) ); + } + + for ( i = 0; i < events.Num(); i++ ) { + events[i]->write( file, "event" ); + } + + fov.write( file, "fov" ); + + s = "}\n"; + FS_Write( s.c_str(), s.length(), file ); + } + FS_FCloseFile( file ); +} + +int idCameraDef::sortEvents( const void *p1, const void *p2 ) { + idCameraEvent *ev1 = (idCameraEvent*)( p1 ); + idCameraEvent *ev2 = (idCameraEvent*)( p2 ); + + if ( ev1->getTime() > ev2->getTime() ) { + return -1; + } + if ( ev1->getTime() < ev2->getTime() ) { + return 1; + } + return 0; +} + +void idCameraDef::addEvent( idCameraEvent *event ) { + events.Append( event ); + //events.Sort(&sortEvents); + +} +void idCameraDef::addEvent( idCameraEvent::eventType t, const char *param, long time ) { + addEvent( new idCameraEvent( t, param, time ) ); + buildCamera(); +} + +void idCameraDef::removeEvent( int index ) { + events.RemoveIndex( index ); + buildCamera(); +} + + +const char *idCameraEvent::eventStr[] = { + "NA", + "WAIT", + "TARGETWAIT", + "SPEED", + "TARGET", + "SNAPTARGET", + "FOV", + "CMD", + "TRIGGER", + "STOP", + "CAMERA", + "FADEOUT", + "FADEIN", + "FEATHER" +}; + +void idCameraEvent::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "type" ) == 0 ) { + type = static_cast( atoi( token ) ); + } + else if ( Q_stricmp( key.c_str(), "param" ) == 0 ) { + paramStr = token; + } + else if ( Q_stricmp( key.c_str(), "time" ) == 0 ) { + time = atoi( token ); + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +void idCameraEvent::write( fileHandle_t file, const char *name ) { + idStr s = va( "\t%s {\n", name ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\ttype %d\n", static_cast( type ) ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\tparam \"%s\"\n", paramStr.c_str() ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\ttime %d\n", time ); + FS_Write( s.c_str(), s.length(), file ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + + +const char *idCameraPosition::positionStr[] = { + "Fixed", + "Interpolated", + "Spline", +}; + + + +const idVec3 *idInterpolatedPosition::getPosition( long t ) { + static idVec3 interpolatedPos; + + float velocity = getVelocity( t ); + float timePassed = t - lastTime; + lastTime = t; + + // convert to seconds + timePassed /= 1000; + + float distToTravel = timePassed * velocity; + + idVec3 temp = startPos; + temp -= endPos; + float distance = temp.Length(); + + distSoFar += distToTravel; + float percent = (float)( distSoFar ) / distance; + + if ( percent > 1.0 ) { + percent = 1.0; + } + else if ( percent < 0.0 ) { + percent = 0.0; + } + + // the following line does a straigt calc on percentage of time + // float percent = (float)(startTime + time - t) / time; + + idVec3 v1 = startPos; + idVec3 v2 = endPos; + v1 *= ( 1.0 - percent ); + v2 *= percent; + v1 += v2; + interpolatedPos = v1; + return &interpolatedPos; +} + + +void idCameraFOV::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + const char *token = Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "fov" ) == 0 ) { + fov = atof( token ); + } + else if ( Q_stricmp( key.c_str(), "startFOV" ) == 0 ) { + startFOV = atof( token ); + } + else if ( Q_stricmp( key.c_str(), "endFOV" ) == 0 ) { + endFOV = atof( token ); + } + else if ( Q_stricmp( key.c_str(), "time" ) == 0 ) { + time = atoi( token ); + } + token = Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +bool idCameraPosition::parseToken( const char *key, const char *( *text ) ) { + const char *token = Com_Parse( text ); + if ( Q_stricmp( key, "time" ) == 0 ) { + time = atol( token ); + return true; + } + else if ( Q_stricmp( key, "type" ) == 0 ) { + type = static_cast( atoi( token ) ); + return true; + } + else if ( Q_stricmp( key, "velocity" ) == 0 ) { + long t = atol( token ); + token = Com_Parse( text ); + long d = atol( token ); + token = Com_Parse( text ); + float s = atof( token ); + addVelocity( t, d, s ); + return true; + } + else if ( Q_stricmp( key, "baseVelocity" ) == 0 ) { + baseVelocity = atof( token ); + return true; + } + else if ( Q_stricmp( key, "name" ) == 0 ) { + name = token; + return true; + } + else if ( Q_stricmp( key, "time" ) == 0 ) { + time = atoi( token ); + return true; + } + Com_UngetToken(); + return false; +} + + + +void idFixedPosition::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + + Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "pos" ) == 0 ) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, pos ); + } + else { + Com_UngetToken(); + idCameraPosition::parseToken( key.c_str(), text ); + } + Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +void idInterpolatedPosition::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + + Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "startPos" ) == 0 ) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, startPos ); + } + else if ( Q_stricmp( key.c_str(), "endPos" ) == 0 ) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, endPos ); + } + else { + Com_UngetToken(); + idCameraPosition::parseToken( key.c_str(), text ); + } + Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + + +void idSplinePosition::parse( const char *( *text ) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp( token, "(" ) || !strcmp( token, "}" ) ) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine( text ); + + Com_Parse( text ); + if ( Q_stricmp( key.c_str(), "target" ) == 0 ) { + target.parse( text ); + } + else { + Com_UngetToken(); + idCameraPosition::parseToken( key.c_str(), text ); + } + Com_Parse( text ); + + } while ( 1 ); + + if ( !strcmp( token, "}" ) ) { + break; + } + + } while ( 1 ); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + + + +void idCameraFOV::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tfov %f\n", fov ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tstartFOV %f\n", startFOV ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tendFOV %f\n", endFOV ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\ttime %i\n", time ); + FS_Write( s.c_str(), s.length(), file ); + + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + + +void idCameraPosition::write( fileHandle_t file, const char *p ) { + + idStr s = va( "\t\ttime %i\n", time ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\ttype %i\n", static_cast( type ) ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tname %s\n", name.c_str() ); + FS_Write( s.c_str(), s.length(), file ); + + s = va( "\t\tbaseVelocity %f\n", baseVelocity ); + FS_Write( s.c_str(), s.length(), file ); + + for ( int i = 0; i < velocities.Num(); i++ ) { + s = va( "\t\tvelocity %i %i %f\n", velocities[i]->startTime, velocities[i]->time, velocities[i]->speed ); + FS_Write( s.c_str(), s.length(), file ); + } + +} + +void idFixedPosition::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + idCameraPosition::write( file, p ); + s = va( "\t\tpos ( %f %f %f )\n", pos.x, pos.y, pos.z ); + FS_Write( s.c_str(), s.length(), file ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + +void idInterpolatedPosition::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + idCameraPosition::write( file, p ); + s = va( "\t\tstartPos ( %f %f %f )\n", startPos.x, startPos.y, startPos.z ); + FS_Write( s.c_str(), s.length(), file ); + s = va( "\t\tendPos ( %f %f %f )\n", endPos.x, endPos.y, endPos.z ); + FS_Write( s.c_str(), s.length(), file ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + +void idSplinePosition::write( fileHandle_t file, const char *p ) { + idStr s = va( "\t%s {\n", p ); + FS_Write( s.c_str(), s.length(), file ); + idCameraPosition::write( file, p ); + target.write( file, "target" ); + s = "\t}\n"; + FS_Write( s.c_str(), s.length(), file ); +} + +void idCameraDef::addTarget( const char *name, idCameraPosition::positionType type ) { + idCameraPosition *pos = newFromType( type ); + if ( pos ) { + pos->setName( name ); + targetPositions.Append( pos ); + activeTarget = numTargets() - 1; + if ( activeTarget == 0 ) { + // first one + addEvent( idCameraEvent::EVENT_TARGET, name, 0 ); + } + } +} + +const idVec3 *idSplinePosition::getPosition( long t ) { + static idVec3 interpolatedPos; + + float velocity = getVelocity( t ); + float timePassed = t - lastTime; + lastTime = t; + + // convert to seconds + timePassed /= 1000; + + float distToTravel = timePassed * velocity; + + distSoFar += distToTravel; + double tempDistance = target.totalDistance(); + + double percent = (double)( distSoFar ) / tempDistance; + + double targetDistance = percent * tempDistance; + tempDistance = 0; + + double lastDistance1,lastDistance2; + lastDistance1 = lastDistance2 = 0; + idVec3 temp; + int count = target.numSegments(); + int i; + for ( i = 1; i < count; i++ ) { + temp = *target.getSegmentPoint( i - 1 ); + temp -= *target.getSegmentPoint( i ); + tempDistance += temp.Length(); + if ( i & 1 ) { + lastDistance1 = tempDistance; + } + else { + lastDistance2 = tempDistance; + } + if ( tempDistance >= targetDistance ) { + break; + } + } + + if ( i >= count - 1 ) { + interpolatedPos = *target.getSegmentPoint( i - 1 ); + } + else { +#if 0 + double timeHi = target.getSegmentTime( i + 1 ); + double timeLo = target.getSegmentTime( i - 1 ); + double percent = ( timeHi - t ) / ( timeHi - timeLo ); + idVec3 v1 = *target.getSegmentPoint( i - 1 ); + idVec3 v2 = *target.getSegmentPoint( i + 1 ); + v2 *= ( 1.0 - percent ); + v1 *= percent; + v2 += v1; + interpolatedPos = v2; +#else + if ( lastDistance1 > lastDistance2 ) { + double d = lastDistance2; + lastDistance2 = lastDistance1; + lastDistance1 = d; + } + + idVec3 v1 = *target.getSegmentPoint( i - 1 ); + idVec3 v2 = *target.getSegmentPoint( i ); + double percent = ( lastDistance2 - targetDistance ) / ( lastDistance2 - lastDistance1 ); + v2 *= ( 1.0 - percent ); + v1 *= percent; + v2 += v1; + interpolatedPos = v2; +#endif + } + return &interpolatedPos; + +} diff --git a/libs/splines/splines.h b/libs/splines/splines.h new file mode 100644 index 0000000..952d93e --- /dev/null +++ b/libs/splines/splines.h @@ -0,0 +1,1129 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 __SPLINES_H +#define __SPLINES_H + +#define GTKRADIANT + +#ifdef GTKRADIANT +#include "igl.h" +#endif + +#include "util_list.h" +#include "util_str.h" +#include "math_vector.h" + +typedef int fileHandle_t; + +extern void glBox( idVec3 &color, idVec3 &point, float size ); +extern void glLabeledPoint( idVec3 &color, idVec3 &point, float size, const char *label ); + +static idVec4 blue( 0, 0, 1, 1 ); +static idVec4 red( 1, 0, 0, 1 ); + +class idPointListInterface { +public: +idPointListInterface() { + selectedPoints.Clear(); +}; +virtual ~idPointListInterface() {}; + +virtual int numPoints() { + return 0; +} + +virtual void addPoint( const float x, const float y, const float z ) {} +virtual void addPoint( const idVec3 &v ) {} +virtual void removePoint( int index ) {} +virtual idVec3 *getPoint( int index ) { return NULL; } + +int selectPointByRay( float ox, float oy, float oz, float dx, float dy, float dz, bool single ) { + idVec3 origin( ox, oy, oz ); + idVec3 dir( dx, dy, dz ); + return selectPointByRay( origin, dir, single ); +} + +int selectPointByRay( const idVec3 origin, const idVec3 direction, bool single ) { + int i, besti, count; + float d, bestd; + idVec3 temp, temp2; + + // find the point closest to the ray + besti = -1; + bestd = 8; + count = numPoints(); + + for ( i = 0; i < count; i++ ) { + temp = *getPoint( i ); + temp2 = temp; + temp -= origin; + d = DotProduct( temp, direction ); + __VectorMA( origin, d, direction, temp ); + temp2 -= temp; + d = temp2.Length(); + if ( d <= bestd ) { + bestd = d; + besti = i; + } + } + + if ( besti >= 0 ) { + selectPoint( besti, single ); + } + + return besti; +} + +int isPointSelected( int index ) { + int count = selectedPoints.Num(); + for ( int i = 0; i < count; i++ ) { + if ( selectedPoints[i] == index ) { + return i; + } + } + return -1; +} + +int selectPoint( int index, bool single ) { + if ( index >= 0 && index < numPoints() ) { + if ( single ) { + deselectAll(); + } + else { + if ( isPointSelected( index ) >= 0 ) { + selectedPoints.Remove( index ); + } + } + return selectedPoints.Append( index ); + } + return -1; +} + +void selectAll() { + selectedPoints.Clear(); + for ( int i = 0; i < numPoints(); i++ ) { + selectedPoints.Append( i ); + } +} + +void deselectAll() { + selectedPoints.Clear(); +} + +int numSelectedPoints(); + +idVec3 *getSelectedPoint( int index ) { + assert( index >= 0 && index < numSelectedPoints() ); + return getPoint( selectedPoints[index] ); +} + +virtual void updateSelection( float x, float y, float z ) { + idVec3 move( x, y, z ); + updateSelection( move ); +} + +virtual void updateSelection( const idVec3 &move ) { + int count = selectedPoints.Num(); + for ( int i = 0; i < count; i++ ) { + *getPoint( selectedPoints[i] ) += move; + } +} + +void drawSelection() { + int count = selectedPoints.Num(); + for ( int i = 0; i < count; i++ ) { + glBox( red, *getPoint( selectedPoints[i] ), 4 ); + } +} + +protected: +idList selectedPoints; + +}; + + +class idSplineList { + +public: + +idSplineList() { + clear(); +} + +idSplineList( const char *p ) { + clear(); + name = p; +}; + +~idSplineList() { + clear(); +}; + +void clearControl() { + for ( int i = 0; i < controlPoints.Num(); i++ ) { + delete controlPoints[i]; + } + controlPoints.Clear(); +} + +void clearSpline() { + for ( int i = 0; i < splinePoints.Num(); i++ ) { + delete splinePoints[i]; + } + splinePoints.Clear(); +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +void clear() { + clearControl(); + clearSpline(); + splineTime.Clear(); + selected = NULL; + dirty = true; + activeSegment = 0; + granularity = 0.025f; + pathColor.set( 1.0f, 0.5f, 0.0f ); + controlColor.set( 0.7f, 0.0f, 1.0f ); + segmentColor.set( 0.0f, 0.0f, 1.0f ); + activeColor.set( 1.0f, 0.0f, 0.0f ); +} + +void initPosition( long startTime, long totalTime ); +const idVec3 *getPosition( long time ); + + +void draw( bool editMode ); +void addToRenderer(); + +void setSelectedPoint( idVec3 *p ); +idVec3 *getSelectedPoint() { + return selected; +} + +void addPoint( const idVec3 &v ) { + controlPoints.Append( new idVec3( v ) ); + dirty = true; +} + +void addPoint( float x, float y, float z ) { + controlPoints.Append( new idVec3( x, y, z ) ); + dirty = true; +} + +void updateSelection( const idVec3 &move ); + +void startEdit() { + editMode = true; +} + +void stopEdit() { + editMode = false; +} + +void buildSpline(); + +void setGranularity( float f ) { + granularity = f; +} + +float getGranularity() { + return granularity; +} + +int numPoints() { + return controlPoints.Num(); +} + +idVec3 *getPoint( int index ) { + assert( index >= 0 && index < controlPoints.Num() ); + return controlPoints[index]; +} + +idVec3 *getSegmentPoint( int index ) { + assert( index >= 0 && index < splinePoints.Num() ); + return splinePoints[index]; +} + + +void setSegmentTime( int index, int time ) { + assert( index >= 0 && index < splinePoints.Num() ); + splineTime[index] = time; +} + +int getSegmentTime( int index ) { + assert( index >= 0 && index < splinePoints.Num() ); + return (int)splineTime[index]; +} +void addSegmentTime( int index, int time ) { + assert( index >= 0 && index < splinePoints.Num() ); + splineTime[index] += time; +} + +float totalDistance(); + +static idVec3 zero; + +int getActiveSegment() { + return activeSegment; +} + +void setActiveSegment( int i ) { + //assert(i >= 0 && (splinePoints.Num() > 0 && i < splinePoints.Num())); + activeSegment = i; +} + +int numSegments() { + return splinePoints.Num(); +} + +void setColors( idVec3 &path, idVec3 &segment, idVec3 &control, idVec3 &active ) { + pathColor = path; + segmentColor = segment; + controlColor = control; + activeColor = active; +} + +const char *getName() { + return name.c_str(); +} + +void setName( const char *p ) { + name = p; +} + +bool validTime() { + if ( dirty ) { + buildSpline(); + } + // gcc doesn't allow static casting away from bools + // why? I've no idea... + return (bool)( splineTime.Num() > 0 && splineTime.Num() == splinePoints.Num() ); +} + +void setTime( long t ) { + time = t; +} + +void setBaseTime( long t ) { + baseTime = t; +} + +protected: +idStr name; +float calcSpline( int step, float tension ); +idList controlPoints; +idList splinePoints; +idList splineTime; +idVec3 *selected; +idVec3 pathColor, segmentColor, controlColor, activeColor; +float granularity; +bool editMode; +bool dirty; +int activeSegment; +long baseTime; +long time; +friend class idCamera; +}; + +// time in milliseconds +// velocity where 1.0 equal rough walking speed +struct idVelocity { + idVelocity( long start, long duration, float s ) { + startTime = start; + time = duration; + speed = s; + } + long startTime; + long time; + float speed; +}; + +// can either be a look at or origin position for a camera +// +class idCameraPosition : public idPointListInterface { +public: + +virtual void clearVelocities() { + for ( int i = 0; i < velocities.Num(); i++ ) { + delete velocities[i]; + velocities[i] = NULL; + } + velocities.Clear(); +} + +virtual void clear() { + editMode = false; + clearVelocities(); +} + +idCameraPosition( const char *p ) { + name = p; +} + +idCameraPosition() { + time = 0; + name = "position"; +} + +idCameraPosition( long t ) { + time = t; +} + +virtual ~idCameraPosition() { + clear(); +} + + +// this can be done with RTTI syntax but i like the derived classes setting a type +// makes serialization a bit easier to see +// +enum positionType { + FIXED = 0x00, + INTERPOLATED, + SPLINE, + POSITION_COUNT +}; + + +virtual void start( long t ) { + startTime = t; +} + +long getTime() { + return time; +} + +virtual void setTime( long t ) { + time = t; +} + +float getBaseVelocity() { + return baseVelocity; +} + +float getVelocity( long t ) { + long check = t - startTime; + for ( int i = 0; i < velocities.Num(); i++ ) { + if ( check >= velocities[i]->startTime && check <= velocities[i]->startTime + velocities[i]->time ) { + return velocities[i]->speed; + } + } + return baseVelocity; +} + +void addVelocity( long start, long duration, float speed ) { + velocities.Append( new idVelocity( start, duration, speed ) ); +} + +virtual const idVec3 *getPosition( long t ) { + assert( true ); + return NULL; +} + +virtual void draw( bool editMode ) {}; + +virtual void parse( const char *( *text ) ) {}; +virtual void write( fileHandle_t file, const char *name ); +virtual bool parseToken( const char *key, const char *( *text ) ); + +const char *getName() { + return name.c_str(); +} + +void setName( const char *p ) { + name = p; +} + +virtual void startEdit() { + editMode = true; +} + +virtual void stopEdit() { + editMode = false; +} + +virtual void draw() {}; + +const char *typeStr() { + return positionStr[static_cast( type )]; +} + +void calcVelocity( float distance ) { + float secs = (float)time / 1000; + baseVelocity = distance / secs; +} + +protected: +static const char* positionStr[POSITION_COUNT]; +long startTime; +long time; +idCameraPosition::positionType type; +idStr name; +bool editMode; +idList velocities; +float baseVelocity; +}; + +class idFixedPosition : public idCameraPosition { +public: + +void init() { + pos.Zero(); + type = idCameraPosition::FIXED; +} + +idFixedPosition() : idCameraPosition() { + init(); +} + +idFixedPosition( idVec3 p ) : idCameraPosition() { + init(); + pos = p; +} + +virtual void addPoint( const idVec3 &v ) { + pos = v; +} + +virtual void addPoint( const float x, const float y, const float z ) { + pos.set( x, y, z ); +} + + +~idFixedPosition() { +} + +virtual const idVec3 *getPosition( long t ) { + return &pos; +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +virtual int numPoints() { + return 1; +} + +virtual idVec3 *getPoint( int index ) { + if ( index != 0 ) { + assert( true ); + } + ; + return &pos; +} + +virtual void draw( bool editMode ) { + glLabeledPoint( blue, pos, ( editMode ) ? 5 : 3, "Fixed point" ); +} + +protected: +idVec3 pos; +}; + +class idInterpolatedPosition : public idCameraPosition { +public: + +void init() { + type = idCameraPosition::INTERPOLATED; + first = true; + startPos.Zero(); + endPos.Zero(); +} + +idInterpolatedPosition() : idCameraPosition() { + init(); +} + +idInterpolatedPosition( idVec3 start, idVec3 end, long time ) : idCameraPosition( time ) { + init(); + startPos = start; + endPos = end; +} + +~idInterpolatedPosition() { +} + +virtual const idVec3 *getPosition( long t ); + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +virtual int numPoints() { + return 2; +} + +virtual idVec3 *getPoint( int index ) { + assert( index >= 0 && index < 2 ); + if ( index == 0 ) { + return &startPos; + } + return &endPos; +} + +virtual void addPoint( const float x, const float y, const float z ) { + if ( first ) { + startPos.set( x, y, z ); + first = false; + } + else { + endPos.set( x, y, z ); + first = true; + } +} + +virtual void addPoint( const idVec3 &v ) { + if ( first ) { + startPos = v; + first = false; + } + else { + endPos = v; + first = true; + } +} + +virtual void draw( bool editMode ) { + glLabeledPoint( blue, startPos, ( editMode ) ? 5 : 3, "Start interpolated" ); + glLabeledPoint( blue, endPos, ( editMode ) ? 5 : 3, "End interpolated" ); + glBegin( GL_LINES ); + glVertex3fv( startPos ); + glVertex3fv( endPos ); + glEnd(); +} + +virtual void start( long t ) { + idCameraPosition::start( t ); + lastTime = startTime; + distSoFar = 0.0; + idVec3 temp = startPos; + temp -= endPos; + calcVelocity( temp.Length() ); +} + +protected: +bool first; +idVec3 startPos; +idVec3 endPos; +long lastTime; +float distSoFar; +}; + +class idSplinePosition : public idCameraPosition { +public: + +void init() { + type = idCameraPosition::SPLINE; +} + +idSplinePosition() : idCameraPosition() { + init(); +} + +idSplinePosition( long time ) : idCameraPosition( time ) { + init(); +} + +~idSplinePosition() { +} + +virtual void start( long t ) { + idCameraPosition::start( t ); + target.initPosition( t, time ); + lastTime = startTime; + distSoFar = 0.0; + calcVelocity( target.totalDistance() ); +} + +//virtual const idVec3 *getPosition(long t) { +// return target.getPosition(t); +//} +virtual const idVec3 *getPosition( long t ); + + +//virtual const idVec3 *getPosition(long t) const { + +void addControlPoint( idVec3 &v ) { + target.addPoint( v ); +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +virtual int numPoints() { + return target.numPoints(); +} + +virtual idVec3 *getPoint( int index ) { + return target.getPoint( index ); +} + +virtual void addPoint( const idVec3 &v ) { + target.addPoint( v ); +} + +virtual void addPoint( const float x, const float y, const float z ) { + target.addPoint( x, y, z ); +} + +virtual void draw( bool editMode ) { + target.draw( editMode ); +} + +virtual void updateSelection( const idVec3 &move ) { + idCameraPosition::updateSelection( move ); + target.buildSpline(); +} + +protected: +idSplineList target; +long lastTime; +float distSoFar; +}; + +class idCameraFOV { +public: + +idCameraFOV() { + time = 0; + length = 0; + fov = 90; +} + +idCameraFOV( int v ) { + time = 0; + length = 0; + fov = v; +} + +idCameraFOV( int s, int e, long t ) { + startFOV = s; + endFOV = e; + length = t; +} + + +~idCameraFOV(){} + +void setFOV( float f ) { + fov = f; +} + +float getFOV( long t ) { + if ( length ) { + float percent = ( t - startTime ) / length; + if ( percent < 0.0 ) { + percent = 0.0; + } + else if ( percent > 1.0 ) { + percent = 1.0; + } + float temp = endFOV - startFOV; + temp *= percent; + fov = startFOV + temp; + + if ( percent == 1.0 ) { + length = 0.0; + } + } + return fov; +} + +void start( long t ) { + startTime = t; +} + +void reset( float startfov, float endfov, int start, float len ) { + startFOV = startfov; + endFOV = endfov; + startTime = start; + length = len * 1000; +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +protected: +float fov; +float startFOV; +float endFOV; +int startTime; +int time; +float length; +}; + + + + +class idCameraEvent { +public: // parameters +enum eventType { + 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 +}; + +static const char* eventStr[EVENT_COUNT]; + +idCameraEvent() { + paramStr = ""; + type = EVENT_NA; + time = 0; +} + +idCameraEvent( eventType t, const char *param, long n ) { + type = t; + paramStr = param; + time = n; +} + +~idCameraEvent() {}; + +eventType getType() { + return type; +} + +const char *typeStr() { + return eventStr[static_cast( type )]; +} + +const char *getParam() { + return paramStr.c_str(); +} + +long getTime() { + return time; +} + +void setTime( long n ) { + time = n; +} + +void parse( const char *( *text ) ); +void write( fileHandle_t file, const char *name ); + +void setTriggered( bool b ) { + triggered = b; +} + +bool getTriggered() { + return triggered; +} + +protected: +eventType type; +idStr paramStr; +long time; +bool triggered; + +}; + +class idCameraDef { +public: + +void clear() { + currentCameraPosition = 0; + cameraRunning = false; + lastDirection.Zero(); + baseTime = 30; + activeTarget = 0; + name = "camera01"; + fov.setFOV( 90 ); + int i; + for ( i = 0; i < targetPositions.Num(); i++ ) { + delete targetPositions[i]; + } + for ( i = 0; i < events.Num(); i++ ) { + delete events[i]; + } + delete cameraPosition; + cameraPosition = NULL; + events.Clear(); + targetPositions.Clear(); +} + +idCameraPosition *startNewCamera( idCameraPosition::positionType type ) { + clear(); + if ( type == idCameraPosition::SPLINE ) { + cameraPosition = new idSplinePosition(); + } + else if ( type == idCameraPosition::INTERPOLATED ) { + cameraPosition = new idInterpolatedPosition(); + } + else { + cameraPosition = new idFixedPosition(); + } + return cameraPosition; +} + +idCameraDef() { + cameraPosition = NULL; + clear(); +} + +~idCameraDef() { + clear(); +} + +void addEvent( idCameraEvent::eventType t, const char *param, long time ); + +void addEvent( idCameraEvent *event ); + +void removeEvent( int index ); + +static int sortEvents( const void *p1, const void *p2 ); + +int numEvents() { + return events.Num(); +} + +idCameraEvent *getEvent( int index ) { + assert( index >= 0 && index < events.Num() ); + return events[index]; +} + +void parse( const char *( *text ) ); +bool load( const char *filename ); +void save( const char *filename ); + +void buildCamera(); + +//idSplineList *getcameraPosition() { +// return &cameraPosition; +//} + +static idCameraPosition *newFromType( idCameraPosition::positionType t ) { + switch ( t ) { + case idCameraPosition::FIXED: return new idFixedPosition(); + case idCameraPosition::INTERPOLATED: return new idInterpolatedPosition(); + case idCameraPosition::SPLINE: return new idSplinePosition(); + default: + break; + }; + return NULL; +} + +void addTarget( const char *name, idCameraPosition::positionType type ); + +idCameraPosition *getActiveTarget() { + if ( targetPositions.Num() == 0 ) { + addTarget( NULL, idCameraPosition::FIXED ); + } + return targetPositions[activeTarget]; +} + +idCameraPosition *getActiveTarget( int index ) { + if ( targetPositions.Num() == 0 ) { + addTarget( NULL, idCameraPosition::FIXED ); + return targetPositions[0]; + } + return targetPositions[index]; +} + +int numTargets() { + return targetPositions.Num(); +} + + +void setActiveTargetByName( const char *name ) { + for ( int i = 0; i < targetPositions.Num(); i++ ) { + if ( Q_stricmp( name, targetPositions[i]->getName() ) == 0 ) { + setActiveTarget( i ); + return; + } + } +} + +void setActiveTarget( int index ) { + assert( index >= 0 && index < targetPositions.Num() ); + activeTarget = index; +} + +void setRunning( bool b ) { + cameraRunning = b; +} + +void setBaseTime( float f ) { + baseTime = f; +} + +float getBaseTime() { + return baseTime; +} + +float getTotalTime() { + return totalTime; +} + +void startCamera( long t ); +void stopCamera() { + cameraRunning = true; +} +void getActiveSegmentInfo( int segment, idVec3 &origin, idVec3 &direction, float *fv ); + +bool getCameraInfo( long time, idVec3 &origin, idVec3 &direction, float *fv ); +bool getCameraInfo( long time, float *origin, float *direction, float *fv ) { + idVec3 org, dir; + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + dir[0] = direction[0]; + dir[1] = direction[1]; + dir[2] = direction[2]; + bool b = getCameraInfo( time, org, dir, fv ); + origin[0] = org[0]; + origin[1] = org[1]; + origin[2] = org[2]; + direction[0] = dir[0]; + direction[1] = dir[1]; + direction[2] = dir[2]; + return b; +} + +void draw( bool editMode ) { + // gcc doesn't allow casting away from bools + // why? I've no idea... + if ( cameraPosition ) { + cameraPosition->draw( (bool)( ( editMode || cameraRunning ) && cameraEdit ) ); + int count = targetPositions.Num(); + for ( int i = 0; i < count; i++ ) { + targetPositions[i]->draw( (bool)( ( editMode || cameraRunning ) && i == activeTarget && !cameraEdit ) ); + } + } +} + +/* + int numSegments() { + if (cameraEdit) { + return cameraPosition.numSegments(); + } + return getTargetSpline()->numSegments(); + } + + int getActiveSegment() { + if (cameraEdit) { + return cameraPosition.getActiveSegment(); + } + return getTargetSpline()->getActiveSegment(); + } + + void setActiveSegment(int i) { + if (cameraEdit) { + cameraPosition.setActiveSegment(i); + } else { + getTargetSpline()->setActiveSegment(i); + } + } + */ +int numPoints() { + if ( cameraEdit ) { + return cameraPosition->numPoints(); + } + return getActiveTarget()->numPoints(); +} + +const idVec3 *getPoint( int index ) { + if ( cameraEdit ) { + return cameraPosition->getPoint( index ); + } + return getActiveTarget()->getPoint( index ); +} + +void stopEdit() { + editMode = false; + if ( cameraEdit ) { + cameraPosition->stopEdit(); + } + else { + getActiveTarget()->stopEdit(); + } +} + +void startEdit( bool camera ) { + cameraEdit = camera; + if ( camera ) { + cameraPosition->startEdit(); + for ( int i = 0; i < targetPositions.Num(); i++ ) { + targetPositions[i]->stopEdit(); + } + } + else { + getActiveTarget()->startEdit(); + cameraPosition->stopEdit(); + } + editMode = true; +} + +bool waitEvent( int index ); + +const char *getName() { + return name.c_str(); +} + +void setName( const char *p ) { + name = p; +} + +idCameraPosition *getPositionObj() { + if ( cameraPosition == NULL ) { + cameraPosition = new idFixedPosition(); + } + return cameraPosition; +} + +protected: +idStr name; +int currentCameraPosition; +idVec3 lastDirection; +bool cameraRunning; +idCameraPosition *cameraPosition; +idList targetPositions; +idList events; +idCameraFOV fov; +int activeTarget; +float totalTime; +float baseTime; +long startTime; + +bool cameraEdit; +bool editMode; +}; + +extern bool g_splineMode; + +extern idCameraDef *g_splineList; + + +#endif diff --git a/libs/splines/util_list.h b/libs/splines/util_list.h new file mode 100644 index 0000000..dfa92fb --- /dev/null +++ b/libs/splines/util_list.h @@ -0,0 +1,347 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 __UTIL_LIST_H__ +#define __UTIL_LIST_H__ + +#include +#include + +template< class type > +class idList { +private: +int m_num; +int m_size; +int m_granularity; +type *m_list; + +public: +idList( int granularity = 16 ); +~idList(); +void Clear( void ); +int Num( void ); +void SetNum( int num ); +void SetGranularity( int granularity ); +void Condense( void ); +int Size( void ); +void Resize( int size ); +type operator[]( int index ) const; +type &operator[]( int index ); +int Append( type const & obj ); +int AddUnique( type const & obj ); +type *Find( type const & obj, int *index = NULL ); +bool RemoveIndex( int index ); +bool Remove( type const & obj ); +typedef int cmp_t ( const void *, const void * ); +void Sort( cmp_t *compare ); +}; + +/* + ================ + idList::idList( int ) + ================ + */ +template< class type > +inline idList::idList( int granularity ) { + assert( granularity > 0 ); + + m_list = NULL; + m_granularity = granularity; + Clear(); +} + +/* + ================ + idList::~idList + ================ + */ +template< class type > +inline idList::~idList() { + Clear(); +} + +/* + ================ + idList::Clear + ================ + */ +template< class type > +inline void idList::Clear( void ) { + if ( m_list ) { + delete[] m_list; + } + + m_list = NULL; + m_num = 0; + m_size = 0; +} + +/* + ================ + idList::Num + ================ + */ +template< class type > +inline int idList::Num( void ) { + return m_num; +} + +/* + ================ + idList::SetNum + ================ + */ +template< class type > +inline void idList::SetNum( int num ) { + assert( num >= 0 ); + if ( num > m_size ) { + // resize it up to the closest level of granularity + Resize( ( ( num + m_granularity - 1 ) / m_granularity ) * m_granularity ); + } + m_num = num; +} + +/* + ================ + idList::SetGranularity + ================ + */ +template< class type > +inline void idList::SetGranularity( int granularity ) { + int newsize; + + assert( granularity > 0 ); + m_granularity = granularity; + + if ( m_list ) { + // resize it to the closest level of granularity + newsize = ( ( m_num + m_granularity - 1 ) / m_granularity ) * m_granularity; + if ( newsize != m_size ) { + Resize( newsize ); + } + } +} + +/* + ================ + idList::Condense + + Resizes the array to exactly the number of elements it contains + ================ + */ +template< class type > +inline void idList::Condense( void ) { + if ( m_list ) { + if ( m_num ) { + Resize( m_num ); + } + else { + Clear(); + } + } +} + +/* + ================ + idList::Size + ================ + */ +template< class type > +inline int idList::Size( void ) { + return m_size; +} + +/* + ================ + idList::Resize + ================ + */ +template< class type > +inline void idList::Resize( int size ) { + type *temp; + int i; + + assert( size > 0 ); + + if ( size <= 0 ) { + Clear(); + return; + } + + temp = m_list; + m_size = size; + if ( m_size < m_num ) { + m_num = m_size; + } + + m_list = new type[ m_size ]; + for ( i = 0; i < m_num; i++ ) { + m_list[ i ] = temp[ i ]; + } + + if ( temp ) { + delete[] temp; + } +} + +/* + ================ + idList::operator[] const + ================ + */ +template< class type > +inline type idList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < m_num ); + + return m_list[ index ]; +} + +/* + ================ + idList::operator[] + ================ + */ +template< class type > +inline type &idList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < m_num ); + + return m_list[ index ]; +} + +/* + ================ + idList::Append + ================ + */ +template< class type > +inline int idList::Append( type const & obj ) { + if ( !m_list ) { + Resize( m_granularity ); + } + + if ( m_num == m_size ) { + Resize( m_size + m_granularity ); + } + + m_list[ m_num ] = obj; + m_num++; + + return m_num - 1; +} + +/* + ================ + idList::AddUnique + ================ + */ +template< class type > +inline int idList::AddUnique( type const & obj ) { + int index; + + if ( !Find( obj, &index ) ) { + index = Append( obj ); + } + + return index; +} + +/* + ================ + idList::Find + ================ + */ +template< class type > +inline type *idList::Find( type const & obj, int *index ) { + int i; + + for ( i = 0; i < m_num; i++ ) { + if ( m_list[ i ] == obj ) { + if ( index ) { + *index = i; + } + return &m_list[ i ]; + } + } + + return NULL; +} + +/* + ================ + idList::RemoveIndex + ================ + */ +template< class type > +inline bool idList::RemoveIndex( int index ) { + int i; + + if ( !m_list || !m_num ) { + return false; + } + + assert( index >= 0 ); + assert( index < m_num ); + + if ( ( index < 0 ) || ( index >= m_num ) ) { + return false; + } + + m_num--; + for ( i = index; i < m_num; i++ ) { + m_list[ i ] = m_list[ i + 1 ]; + } + + return true; +} + +/* + ================ + idList::Remove + ================ + */ +template< class type > +inline bool idList::Remove( type const & obj ) { + int index; + + if ( Find( obj, &index ) ) { + return RemoveIndex( index ); + } + + return false; +} + +/* + ================ + idList::Sort + ================ + */ +template< class type > +inline void idList::Sort( cmp_t *compare ) { + if ( !m_list ) { + return; + } + + qsort( ( void * )m_list, ( size_t )m_num, sizeof( type ), compare ); +} + +#endif /* !__UTIL_LIST_H__ */ diff --git a/libs/splines/util_str.cpp b/libs/splines/util_str.cpp new file mode 100644 index 0000000..c64ca73 --- /dev/null +++ b/libs/splines/util_str.cpp @@ -0,0 +1,577 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//need to rewrite this + +#include "util_str.h" +#include +#include +#include +#include + +#if GDEF_COMPILER_MSVC +#pragma warning(disable : 4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +static const int STR_ALLOC_GRAN = 20; + +// screwy but intentional +#ifdef __APPLE_BUG__ +char *idStr::__tolower +#else +char *idStr::tolower +#endif +( + char *s1 +){ + char *s; + + s = s1; + while ( *s ) + { + *s = ::tolower( *s ); + s++; + } + + return s1; +} + +// screwy but intentional +#ifdef __APPLE_BUG__ +char *idStr::__toupper +#else +char *idStr::toupper +#endif +( + char *s1 +){ + char *s; + + s = s1; + while ( *s ) + { + *s = ::toupper( *s ); + s++; + } + + return s1; +} + +int idStr::icmpn +( + const char *s1, + const char *s2, + int n +){ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + // idStrings are equal until end point + return 0; + } + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 < c2 ) { + // strings less than + return -1; + } + else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +int idStr::icmp +( + const char *s1, + const char *s2 +){ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 < c2 ) { + // strings less than + return -1; + } + else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +int idStr::cmpn +( + const char *s1, + const char *s2, + int n +){ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + // strings are equal until end point + return 0; + } + + if ( c1 < c2 ) { + // strings less than + return -1; + } + else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +int idStr::cmp +( + const char *s1, + const char *s2 +){ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( c1 < c2 ) { + // strings less than + return -1; + } + else if ( c1 > c2 ) { + // strings greater than + return 1; + } + } + while ( c1 ); + + // strings are equal + return 0; +} + +/* + ============ + IsNumeric + + Checks a string to see if it contains only numerical values. + ============ + */ +bool idStr::isNumeric +( + const char *str +){ + int len; + int i; + bool dot; + + if ( *str == '-' ) { + str++; + } + + dot = false; + len = strlen( str ); + for ( i = 0; i < len; i++ ) + { + if ( !isdigit( str[ i ] ) ) { + if ( ( str[ i ] == '.' ) && !dot ) { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +idStr operator+ +( + const idStr& a, + const float b +){ + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%f", b ); + result.append( text ); + + return result; +} + +idStr operator+ +( + const idStr& a, + const int b +){ + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%d", b ); + result.append( text ); + + return result; +} + +idStr operator+ +( + const idStr& a, + const unsigned b +){ + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%u", b ); + result.append( text ); + + return result; +} + +idStr& idStr::operator+= +( + const float a +){ + char text[ 20 ]; + + sprintf( text, "%f", a ); + append( text ); + + return *this; +} + +idStr& idStr::operator+= +( + const int a +){ + char text[ 20 ]; + + sprintf( text, "%d", a ); + append( text ); + + return *this; +} + +idStr& idStr::operator+= +( + const unsigned a +){ + char text[ 20 ]; + + sprintf( text, "%u", a ); + append( text ); + + return *this; +} + +void idStr::CapLength +( + int newlen +){ + assert( m_data ); + + if ( length() <= newlen ) { + return; + } + + EnsureDataWritable(); + + m_data->data[newlen] = 0; + m_data->len = newlen; +} + +void idStr::EnsureDataWritable +( + void +){ + assert( m_data ); + strdata *olddata; + int len; + + if ( !m_data->refcount ) { + return; + } + + olddata = m_data; + len = length(); + + m_data = new strdata; + + EnsureAlloced( len + 1, false ); + strncpy( m_data->data, olddata->data, len + 1 ); + m_data->len = len; + + olddata->DelRef(); +} + +void idStr::EnsureAlloced( int amount, bool keepold ) { + + if ( !m_data ) { + m_data = new strdata(); + } + + // Now, let's make sure it's writable + EnsureDataWritable(); + + char *newbuffer; + bool wasalloced = ( m_data->alloced != 0 ); + + if ( amount < m_data->alloced ) { + return; + } + + assert( amount ); + if ( amount == 1 ) { + m_data->alloced = 1; + } + else { + int newsize, mod; + mod = amount % STR_ALLOC_GRAN; + if ( !mod ) { + newsize = amount; + } + else { + newsize = amount + STR_ALLOC_GRAN - mod; + } + m_data->alloced = newsize; + } + + newbuffer = new char[m_data->alloced]; + if ( wasalloced && keepold ) { + strcpy( newbuffer, m_data->data ); + } + + if ( m_data->data ) { + delete [] m_data->data; + } + m_data->data = newbuffer; +} + +void idStr::BackSlashesToSlashes +( + void +){ + int i; + + EnsureDataWritable(); + + for ( i = 0; i < m_data->len; i++ ) + { + if ( m_data->data[i] == '\\' ) { + m_data->data[i] = '/'; + } + } +} + +void idStr::snprintf +( + char *dst, + int size, + const char *fmt, + ... +){ + char buffer[0x10000]; + int len; + va_list argptr; + + va_start( argptr,fmt ); + len = vsprintf( buffer,fmt,argptr ); + va_end( argptr ); + + assert( len < size ); + + strncpy( dst, buffer, size - 1 ); +} + +#if GDEF_COMPILER_MSVC +#pragma warning(disable : 4189) // local variable is initialized but not referenced +#endif + +/* + ================= + TestStringClass + + This is a fairly rigorous test of the idStr class's functionality. + Because of the fairly global and subtle ramifications of a bug occuring + in this class, it should be run after any changes to the class. + Add more tests as functionality is changed. Tests should include + any possible bounds violation and NULL data tests. + ================= + */ +void TestStringClass +( + void +){ + char ch; // ch == ? + (void) ch; + idStr *t; // t == ? + idStr a; // a.len == 0, a.data == "\0" + idStr b; // b.len == 0, b.data == "\0" + idStr c( "test" ); // c.len == 4, c.data == "test\0" + idStr d( c ); // d.len == 4, d.data == "test\0" + idStr e( static_cast( NULL ) ); + // e.len == 0, e.data == "\0" ASSERT! + int i; // i == ? + (void) i; + + i = a.length(); // i == 0 + i = c.length(); // i == 4 + + t = new idStr(); // t->len == 0, t->data == "\0" + delete t; // t == ? + + b = "test"; // b.len == 4, b.data == "test\0" + t = new idStr( "test" ); // t->len == 4, t->data == "test\0" + delete t; // t == ? + + a = c; // a.len == 4, a.data == "test\0" +// a = ""; + a = NULL; // a.len == 0, a.data == "\0" ASSERT! + a = c + d; // a.len == 8, a.data == "testtest\0" + a = c + "wow"; // a.len == 7, a.data == "testwow\0" + a = c + static_cast( NULL ); + // a.len == 4, a.data == "test\0" ASSERT! + a = "this" + d; // a.len == 8, a.data == "thistest\0" + a = static_cast( NULL ) + d; + // a.len == 4, a.data == "test\0" ASSERT! + a += c; // a.len == 8, a.data == "testtest\0" + a += "wow"; // a.len == 11, a.data == "testtestwow\0" + a += static_cast( NULL ); + // a.len == 11, a.data == "testtestwow\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + ch = a[ 0 ]; // ch == 't' + ch = a[ -1 ]; // ch == 0 ASSERT! + ch = a[ 1000 ]; // ch == 0 ASSERT! + ch = a[ 0 ]; // ch == 't' + ch = a[ 1 ]; // ch == 'e' + ch = a[ 2 ]; // ch == 's' + ch = a[ 3 ]; // ch == 't' + ch = a[ 4 ]; // ch == '\0' ASSERT! + ch = a[ 5 ]; // ch == '\0' ASSERT! + + a[ 1 ] = 'b'; // a.len == 4, a.data == "tbst\0" + a[ -1 ] = 'b'; // a.len == 4, a.data == "tbst\0" ASSERT! + a[ 0 ] = '0'; // a.len == 4, a.data == "0bst\0" + a[ 1 ] = '1'; // a.len == 4, a.data == "01st\0" + a[ 2 ] = '2'; // a.len == 4, a.data == "012t\0" + a[ 3 ] = '3'; // a.len == 4, a.data == "0123\0" + a[ 4 ] = '4'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 5 ] = '5'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 7 ] = '7'; // a.len == 4, a.data == "0123\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + b = "no"; // b.len == 2, b.data == "no\0" + + i = ( a == b ); // i == 0 + i = ( a == c ); // i == 1 + + i = ( a == "blow" ); // i == 0 + i = ( a == "test" ); // i == 1 + i = ( a == NULL ); // i == 0 ASSERT! + + i = ( "test" == b ); // i == 0 + i = ( "test" == a ); // i == 1 + i = ( NULL == a ); // i == 0 ASSERT! + + i = ( a != b ); // i == 1 + i = ( a != c ); // i == 0 + + i = ( a != "blow" ); // i == 1 + i = ( a != "test" ); // i == 0 + i = ( a != NULL ); // i == 1 ASSERT! + + i = ( "test" != b ); // i == 1 + i = ( "test" != a ); // i == 0 + i = ( NULL != a ); // i == 1 ASSERT! + + a = "test"; // a.data == "test" + b = a; // b.data == "test" + + a = "not"; // a.data == "not", b.data == "test" + + a = b; // a.data == b.data == "test" + + a += b; // a.data == "testtest", b.data = "test" + + a = b; + + a[1] = '1'; // a.data = "t1st", b.data = "test" +} + +#if GDEF_COMPILER_MSVC +#pragma warning(default : 4189) // local variable is initialized but not referenced +#pragma warning(disable : 4514) // unreferenced inline function has been removed +#endif diff --git a/libs/splines/util_str.h b/libs/splines/util_str.h new file mode 100644 index 0000000..02240ce --- /dev/null +++ b/libs/splines/util_str.h @@ -0,0 +1,721 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//need to rewrite this + +#ifndef __UTIL_STR_H__ +#define __UTIL_STR_H__ + +#include +#include +#include + +#if GDEF_COMPILER_MSVC +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +void TestStringClass(); + +class strdata +{ +public: +strdata () : len( 0 ), refcount( 0 ), data( NULL ), alloced( 0 ) {} +~strdata (){ + if ( data ) { + delete [] data; + } +} + +void AddRef() { refcount++; } +bool DelRef(){ // True if killed + refcount--; + if ( refcount < 0 ) { + delete this; + return true; + } + + return false; +} + +int len; +int refcount; +char *data; +int alloced; +}; + +class idStr { +protected: +strdata *m_data; +void EnsureAlloced( int, bool keepold = true ); +void EnsureDataWritable(); + +public: +~idStr(); +idStr(); +idStr( const char *text ); +idStr( const idStr& string ); +idStr( const idStr string, int start, int end ); +idStr( const char ch ); +idStr( const int num ); +idStr( const float num ); +idStr( const unsigned num ); +int length( void ) const; +int allocated( void ) const; +const char * c_str( void ) const; + +void append( const char *text ); +void append( const idStr& text ); +char operator[]( int index ) const; +char& operator[]( int index ); + +void operator=( const idStr& text ); +void operator=( const char *text ); + +friend idStr operator+( const idStr& a, const idStr& b ); +friend idStr operator+( const idStr& a, const char *b ); +friend idStr operator+( const char *a, const idStr& b ); + +friend idStr operator+( const idStr& a, const float b ); +friend idStr operator+( const idStr& a, const int b ); +friend idStr operator+( const idStr& a, const unsigned b ); +friend idStr operator+( const idStr& a, const bool b ); +friend idStr operator+( const idStr& a, const char b ); + +idStr& operator+=( const idStr& a ); +idStr& operator+=( const char *a ); +idStr& operator+=( const float a ); +idStr& operator+=( const char a ); +idStr& operator+=( const int a ); +idStr& operator+=( const unsigned a ); +idStr& operator+=( const bool a ); + +friend bool operator==( const idStr& a, const idStr& b ); +friend bool operator==( const idStr& a, const char *b ); +friend bool operator==( const char *a, const idStr& b ); + +friend bool operator!=( const idStr& a, const idStr& b ); +friend bool operator!=( const idStr& a, const char *b ); +friend bool operator!=( const char *a, const idStr& b ); + +operator const char *() const; +operator const char *(); + +int icmpn( const char *text, int n ) const; +int icmpn( const idStr& text, int n ) const; +int icmp( const char *text ) const; +int icmp( const idStr& text ) const; +int cmpn( const char *text, int n ) const; +int cmpn( const idStr& text, int n ) const; +int cmp( const char *text ) const; +int cmp( const idStr& text ) const; + +void tolower( void ); +void toupper( void ); + +static char *tolower( char *s1 ); +static char *toupper( char *s1 ); + +static int icmpn( const char *s1, const char *s2, int n ); +static int icmp( const char *s1, const char *s2 ); +static int cmpn( const char *s1, const char *s2, int n ); +static int cmp( const char *s1, const char *s2 ); + +static void snprintf( char *dst, int size, const char *fmt, ... ); + +static bool isNumeric( const char *str ); +bool isNumeric( void ) const; + +void CapLength( int ); + +void BackSlashesToSlashes(); + +}; + +inline idStr::~idStr(){ + if ( m_data ) { + m_data->DelRef(); + m_data = NULL; + } +} + +inline idStr::idStr() : m_data( NULL ){ + EnsureAlloced( 1 ); + m_data->data[ 0 ] = 0; +} + +inline idStr::idStr +( + const char *text +) : m_data( NULL ){ + int len; + + assert( text ); + + if ( text ) { + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; + } + else + { + EnsureAlloced( 1 ); + m_data->data[ 0 ] = 0; + m_data->len = 0; + } +} + +inline idStr::idStr +( + const idStr& text +) : m_data( NULL ){ + m_data = text.m_data; + m_data->AddRef(); +} + +inline idStr::idStr +( + const idStr text, + int start, + int end +) : m_data( NULL ){ + int i; + int len; + + if ( end > text.length() ) { + end = text.length(); + } + + if ( start > text.length() ) { + start = text.length(); + } + + len = end - start; + if ( len < 0 ) { + len = 0; + } + + EnsureAlloced( len + 1 ); + + for ( i = 0; i < len; i++ ) + { + m_data->data[ i ] = text[ start + i ]; + } + + m_data->data[ len ] = 0; + m_data->len = len; +} + +inline idStr::idStr +( + const char ch +) : m_data( NULL ){ + EnsureAlloced( 2 ); + + m_data->data[ 0 ] = ch; + m_data->data[ 1 ] = 0; + m_data->len = 1; +} + +inline idStr::idStr +( + const float num +) : m_data( NULL ){ + char text[ 32 ]; + int len; + + sprintf( text, "%.3f", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline idStr::idStr +( + const int num +) : m_data( NULL ){ + char text[ 32 ]; + int len; + + sprintf( text, "%d", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline idStr::idStr +( + const unsigned num +) : m_data( NULL ){ + char text[ 32 ]; + int len; + + sprintf( text, "%u", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline int idStr::length( void ) const { + return ( m_data != NULL ) ? m_data->len : 0; +} + +inline int idStr::allocated( void ) const { + return ( m_data != NULL ) ? m_data->alloced + sizeof( *m_data ) : 0; +} + +inline const char *idStr::c_str( void ) const { + assert( m_data ); + + return m_data->data; +} + +inline void idStr::append +( + const char *text +){ + int len; + + assert( text ); + + if ( text ) { + len = length() + strlen( text ); + EnsureAlloced( len + 1 ); + + strcat( m_data->data, text ); + m_data->len = len; + } +} + +inline void idStr::append +( + const idStr& text +){ + int len; + + len = length() + text.length(); + EnsureAlloced( len + 1 ); + + strcat( m_data->data, text.c_str() ); + m_data->len = len; +} + +inline char idStr::operator[]( int index ) const { + assert( m_data ); + + if ( !m_data ) { + return 0; + } + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < m_data->len ) ); + + // In release mode, give them a null character + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= m_data->len ) ) { + return 0; + } + + return m_data->data[ index ]; +} + +inline char& idStr::operator[] +( + int index +){ + // Used for result for invalid indices + static char dummy = 0; + assert( m_data ); + + // We don't know if they'll write to it or not + // if it's not a const object + EnsureDataWritable(); + + if ( !m_data ) { + return dummy; + } + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < m_data->len ) ); + + // In release mode, let them change a safe variable + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= m_data->len ) ) { + return dummy; + } + + return m_data->data[ index ]; +} + +inline void idStr::operator= +( + const idStr& text +){ + // adding the reference before deleting our current reference prevents + // us from deleting our string if we are copying from ourself + text.m_data->AddRef(); + m_data->DelRef(); + m_data = text.m_data; +} + +inline void idStr::operator= +( + const char *text +){ + int len; + + assert( text ); + + if ( !text ) { + // safe behaviour if NULL + EnsureAlloced( 1, false ); + m_data->data[0] = 0; + m_data->len = 0; + return; + } + + if ( !m_data ) { + len = strlen( text ); + EnsureAlloced( len + 1, false ); + strcpy( m_data->data, text ); + m_data->len = len; + return; + } + + if ( text == m_data->data ) { + return; // Copying same thing. Punt. + + } + // If we alias and I don't do this, I could corrupt other strings... This + // will get called with EnsureAlloced anyway + EnsureDataWritable(); + + // Now we need to check if we're aliasing.. + if ( text >= m_data->data && text <= m_data->data + m_data->len ) { + // Great, we're aliasing. We're copying from inside ourselves. + // This means that I don't have to ensure that anything is alloced, + // though I'll assert just in case. + int diff = text - m_data->data; + int i; + + assert( strlen( text ) < (unsigned) m_data->len ); + + for ( i = 0; text[i]; i++ ) + { + m_data->data[i] = text[i]; + } + + m_data->data[i] = 0; + + m_data->len -= diff; + + return; + } + + len = strlen( text ); + EnsureAlloced( len + 1, false ); + strcpy( m_data->data, text ); + m_data->len = len; +} + +inline idStr operator+ +( + const idStr& a, + const idStr& b +){ + idStr result( a ); + + result.append( b ); + + return result; +} + +inline idStr operator+ +( + const idStr& a, + const char *b +){ + idStr result( a ); + + result.append( b ); + + return result; +} + +inline idStr operator+ +( + const char *a, + const idStr& b +){ + idStr result( a ); + + result.append( b ); + + return result; +} + +inline idStr operator+ +( + const idStr& a, + const bool b +){ + idStr result( a ); + + result.append( b ? "true" : "false" ); + + return result; +} + +inline idStr operator+ +( + const idStr& a, + const char b +){ + char text[ 2 ]; + + text[ 0 ] = b; + text[ 1 ] = 0; + + return a + text; +} + +inline idStr& idStr::operator+= +( + const idStr& a +){ + append( a ); + return *this; +} + +inline idStr& idStr::operator+= +( + const char *a +){ + append( a ); + return *this; +} + +inline idStr& idStr::operator+= +( + const char a +){ + char text[ 2 ]; + + text[ 0 ] = a; + text[ 1 ] = 0; + append( text ); + + return *this; +} + +inline idStr& idStr::operator+= +( + const bool a +){ + append( a ? "true" : "false" ); + return *this; +} + +inline bool operator== +( + const idStr& a, + const idStr& b +){ + return ( !strcmp( a.c_str(), b.c_str() ) ); +} + +inline bool operator== +( + const idStr& a, + const char *b +){ + assert( b ); + if ( !b ) { + return false; + } + return ( !strcmp( a.c_str(), b ) ); +} + +inline bool operator== +( + const char *a, + const idStr& b +){ + assert( a ); + if ( !a ) { + return false; + } + return ( !strcmp( a, b.c_str() ) ); +} + +inline bool operator!= +( + const idStr& a, + const idStr& b +){ + return !( a == b ); +} + +inline bool operator!= +( + const idStr& a, + const char *b +){ + return !( a == b ); +} + +inline bool operator!= +( + const char *a, + const idStr& b +){ + return !( a == b ); +} + +inline int idStr::icmpn +( + const char *text, + int n +) const { + assert( m_data ); + assert( text ); + + return idStr::icmpn( m_data->data, text, n ); +} + +inline int idStr::icmpn +( + const idStr& text, + int n +) const { + assert( m_data ); + assert( text.m_data ); + + return idStr::icmpn( m_data->data, text.m_data->data, n ); +} + +inline int idStr::icmp +( + const char *text +) const { + assert( m_data ); + assert( text ); + + return idStr::icmp( m_data->data, text ); +} + +inline int idStr::icmp +( + const idStr& text +) const { + assert( c_str() ); + assert( text.c_str() ); + + return idStr::icmp( c_str(), text.c_str() ); +} + +inline int idStr::cmp +( + const char *text +) const { + assert( m_data ); + assert( text ); + + return idStr::cmp( m_data->data, text ); +} + +inline int idStr::cmp +( + const idStr& text +) const { + assert( c_str() ); + assert( text.c_str() ); + + return idStr::cmp( c_str(), text.c_str() ); +} + +inline int idStr::cmpn +( + const char *text, + int n +) const { + assert( c_str() ); + assert( text ); + + return idStr::cmpn( c_str(), text, n ); +} + +inline int idStr::cmpn +( + const idStr& text, + int n +) const { + assert( c_str() ); + assert( text.c_str() ); + + return idStr::cmpn( c_str(), text.c_str(), n ); +} + +inline void idStr::tolower +( + void +){ + assert( m_data ); + + EnsureDataWritable(); + + idStr::tolower( m_data->data ); +} + +inline void idStr::toupper +( + void +){ + assert( m_data ); + + EnsureDataWritable(); + + idStr::toupper( m_data->data ); +} + +inline bool idStr::isNumeric +( + void +) const { + assert( m_data ); + return idStr::isNumeric( m_data->data ); +} + +inline idStr::operator const char *() { + return c_str(); +} + +inline idStr::operator const char * +( + void +) const { + return c_str(); +} + +#endif diff --git a/libs/str.h b/libs/str.h new file mode 100644 index 0000000..e7ef600 --- /dev/null +++ b/libs/str.h @@ -0,0 +1,472 @@ +/* + Copyright (c) 2001, Loki software, inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of Loki software nor the names of its contributors may be used + to endorse or promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __STR__ +#define __STR__ + +#include "globaldefs.h" + +// +// class Str +// loose replacement for CString from MFC +// + +#include +#include + +#include +#include + +#include + +#if GDEF_COMPILER_MSVC +#define strcasecmp strcmpi +#if _MSC_VER < 1400 +#define vsnprintf std::vsnprintf +#endif +#else +#include +#endif + +// NOTE TTimo __StrDup was initially implemented in pakstuff.cpp +// causing a bunch of issues for broader targets that use Str.h (such as plugins and modules) +// Q_StrDup should be used now, using a #define __StrDup for easy transition + +#define __StrDup Q_StrDup + +inline char* Q_StrDup( const char* pStr ){ + if ( pStr == 0 ) { + pStr = ""; + } + + return strcpy( new char[strlen( pStr ) + 1], pStr ); +} + +#if !GDEF_OS_WINDOWS +#define strcmpi strcasecmp +#define stricmp strcasecmp +#define strnicmp strncasecmp + +inline char* strlwr( char* string ){ + char *cp; + for ( cp = string; *cp; ++cp ) + { + if ( 'A' <= *cp && *cp <= 'Z' ) { + *cp += 'a' - 'A'; + } + } + + return string; +} + +inline char* strupr( char* string ){ + char *cp; + for ( cp = string; *cp; ++cp ) + { + if ( 'a' <= *cp && *cp <= 'z' ) { + *cp += 'A' - 'a'; + } + } + + return string; +} +#endif + +static char *g_pStrWork = 0; + +class Str +{ +protected: +bool m_bIgnoreCase; +char *m_pStr; + +public: +Str(){ + m_bIgnoreCase = true; + m_pStr = new char[1]; + m_pStr[0] = '\0'; +} + +Str( char *p ){ + m_bIgnoreCase = true; + m_pStr = __StrDup( p ); +} + +Str( const char *p ){ + m_bIgnoreCase = true; + m_pStr = __StrDup( p ); +} + +Str( const unsigned char *p ){ + m_bIgnoreCase = true; + m_pStr = __StrDup( reinterpret_cast( p ) ); +} + +Str( const char c ){ + m_bIgnoreCase = true; + m_pStr = new char[2]; + m_pStr[0] = c; + m_pStr[1] = '\0'; +} + +const char* GetBuffer() const { + return m_pStr; +} + +char* GetBuffer(){ + return m_pStr; +} + +Str( const Str &s ){ + m_bIgnoreCase = true; + m_pStr = __StrDup( s.GetBuffer() ); +} + +void Deallocate(){ + delete []m_pStr; + m_pStr = 0; +} + +void Allocate( std::size_t n ){ + Deallocate(); + m_pStr = new char[n]; +} + +void MakeEmpty(){ + Deallocate(); + m_pStr = __StrDup( "" ); +} + +~Str(){ + Deallocate(); + // NOTE TTimo: someone explain this g_pStrWork to me? + if ( g_pStrWork ) { + delete []g_pStrWork; + } + g_pStrWork = 0; +} + +void MakeLower(){ + if ( m_pStr ) { + strlwr( m_pStr ); + } +} + +void MakeUpper(){ + if ( m_pStr ) { + strupr( m_pStr ); + } +} + +void TrimRight(){ + char* lpsz = m_pStr; + char* lpszLast = 0; + while ( *lpsz != '\0' ) + { + if ( isspace( *lpsz ) ) { + if ( lpszLast == 0 ) { + lpszLast = lpsz; + } + } + else{ + lpszLast = 0; + } + lpsz++; + } + + if ( lpszLast != 0 ) { + // truncate at trailing space start + *lpszLast = '\0'; + } +} + +void TrimLeft(){ + // find first non-space character + char* lpsz = m_pStr; + while ( isspace( *lpsz ) ) + lpsz++; + + // fix up data and length + std::size_t nDataLength = GetLength() - ( lpsz - m_pStr ); + memmove( m_pStr, lpsz, ( nDataLength + 1 ) ); +} + +char* Find( const char *p ){ + return strstr( m_pStr, p ); +} + +// search starting at a given offset +char* Find( const char *p, std::size_t offset ){ + return strstr( m_pStr + offset, p ); +} + +char* Find( const char ch ){ + return strchr( m_pStr, ch ); +} + +char* ReverseFind( const char ch ){ + return strrchr( m_pStr, ch ); +} + +int Compare( const char* str ) const { + return strcmp( m_pStr, str ); +} + +int CompareNoCase( const char* str ) const { + return strcasecmp( m_pStr, str ); +} + +std::size_t GetLength(){ + return ( m_pStr ) ? strlen( m_pStr ) : 0; +} + +const char* Left( std::size_t n ){ + delete []g_pStrWork; + if ( n > 0 ) { + g_pStrWork = new char[n + 1]; + strncpy( g_pStrWork, m_pStr, n ); + g_pStrWork[n] = '\0'; + } + else + { + g_pStrWork = new char[1]; + g_pStrWork[0] = '\0'; + } + return g_pStrWork; +} + +const char* Right( std::size_t n ){ + delete []g_pStrWork; + if ( n > 0 ) { + g_pStrWork = new char[n + 1]; + std::size_t nStart = GetLength() - n; + strncpy( g_pStrWork, &m_pStr[nStart], n ); + g_pStrWork[n] = '\0'; + } + else + { + g_pStrWork = new char[1]; + g_pStrWork[0] = '\0'; + } + return g_pStrWork; +} + +const char* Mid( std::size_t nFirst ) const { + return Mid( nFirst, strlen( m_pStr ) - nFirst ); +} + +const char* Mid( std::size_t first, std::size_t n ) const { + delete []g_pStrWork; + if ( n > 0 ) { + g_pStrWork = new char[n + 1]; + strncpy( g_pStrWork, m_pStr + first, n ); + g_pStrWork[n] = '\0'; + } + else + { + g_pStrWork = new char[1]; + g_pStrWork[0] = '\0'; + } + return g_pStrWork; +} + +#if 0 // defined(__G_LIB_H__) +void Format( const char* fmt, ... ){ + va_list args; + char *buffer; + + va_start( args, fmt ); + buffer = g_strdup_vprintf( fmt, args ); + va_end( args ); + + delete[] m_pStr; + m_pStr = __StrDup( buffer ); + g_free( buffer ); +} +#else +void Format( const char* fmt, ... ){ + char buffer[1024]; + + { + va_list args; + va_start( args, fmt ); + vsnprintf( buffer, 1023, fmt, args ); + va_end( args ); + } + + delete[] m_pStr; + m_pStr = __StrDup( buffer ); +} +#endif + +void SetAt( std::size_t n, char ch ){ + if ( n < GetLength() ) { + m_pStr[n] = ch; + } +} + +// NOTE: unlike CString, this looses the pointer +void ReleaseBuffer( std::size_t n ){ + char* tmp = m_pStr; + tmp[n] = '\0'; + m_pStr = __StrDup( tmp ); + delete []tmp; +} +void ReleaseBuffer(){ + ReleaseBuffer( GetLength() ); +} + +char* GetBufferSetLength( std::size_t n ){ + char *p = new char[n + 1]; + strncpy( p, m_pStr, n ); + p[n] = '\0'; + delete []m_pStr; + m_pStr = p; + return m_pStr; +} + +// char& operator *() { return *m_pStr; } +// char& operator *() const { return *const_cast(this)->m_pStr; } +operator void*() { + return m_pStr; +} +operator char*() { + return m_pStr; +} +operator const char*() const { return reinterpret_cast( m_pStr ); } +operator unsigned char*() { + return reinterpret_cast( m_pStr ); +} +operator const unsigned char*() const { return reinterpret_cast( m_pStr ); } +Str& operator =( const Str& rhs ){ + if ( &rhs != this ) { + delete[] m_pStr; + m_pStr = __StrDup( rhs.m_pStr ); + } + return *this; +} + +Str& operator =( const char* pStr ){ + if ( m_pStr != pStr ) { + delete[] m_pStr; + m_pStr = __StrDup( pStr ); + } + return *this; +} + +Str& operator +=( const char ch ){ + std::size_t len = GetLength(); + char *p = new char[len + 1 + 1]; + + if ( m_pStr ) { + strcpy( p, m_pStr ); + delete[] m_pStr; + } + + m_pStr = p; + m_pStr[len] = ch; + m_pStr[len + 1] = '\0'; + + return *this; +} + +Str& operator +=( const char *pStr ){ + if ( pStr ) { + if ( m_pStr ) { + char *p = new char[strlen( m_pStr ) + strlen( pStr ) + 1]; + strcpy( p, m_pStr ); + strcat( p, pStr ); + delete[] m_pStr; + m_pStr = p; + } + else + { + m_pStr = __StrDup( pStr ); + } + } + return *this; +} + + +bool operator ==( const Str& rhs ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, rhs.m_pStr ) == 0 : strcmp( m_pStr, rhs.m_pStr ) == 0; } +bool operator ==( char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) == 0 : strcmp( m_pStr, pStr ) == 0; } +bool operator ==( const char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) == 0 : strcmp( m_pStr, pStr ) == 0; } +bool operator !=( Str& rhs ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, rhs.m_pStr ) != 0 : strcmp( m_pStr, rhs.m_pStr ) != 0; } +bool operator !=( char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) != 0 : strcmp( m_pStr, pStr ) != 0; } +bool operator !=( const char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) != 0 : strcmp( m_pStr, pStr ) != 0; } +bool operator <( const Str& rhs ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, rhs.m_pStr ) < 0 : strcmp( m_pStr, rhs.m_pStr ) < 0; } +bool operator <( char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) < 0 : strcmp( m_pStr, pStr ) < 0; } +bool operator <( const char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) < 0 : strcmp( m_pStr, pStr ) < 0; } +bool operator >( const Str& rhs ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, rhs.m_pStr ) > 0 : strcmp( m_pStr, rhs.m_pStr ) > 0; } +bool operator >( char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) > 0 : strcmp( m_pStr, pStr ) > 0; } +bool operator >( const char* pStr ) const { return ( m_bIgnoreCase ) ? stricmp( m_pStr, pStr ) > 0 : strcmp( m_pStr, pStr ) > 0; } +char& operator []( std::size_t nIndex ) { return m_pStr[nIndex]; } +const char& operator []( std::size_t nIndex ) const { return m_pStr[nIndex]; } +char GetAt( std::size_t nIndex ) { return m_pStr[nIndex]; } +}; + + +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const Str& str ){ + return ostream << str.GetBuffer(); +} + + +inline void AddSlash( Str& strPath ){ + if ( strPath.GetLength() > 0 ) { + if ( ( strPath.GetAt( strPath.GetLength() - 1 ) != '/' ) && + ( strPath.GetAt( strPath.GetLength() - 1 ) != '\\' ) ) { + strPath += '/'; + } + } +} + +inline bool ExtractPath_and_Filename( const char* pPath, Str& strPath, Str& strFilename ){ + Str strPathName; + strPathName = pPath; + const char* substr = strPathName.ReverseFind( '\\' ); + if ( substr == 0 ) { + // TTimo: try forward slash, some are using forward + substr = strPathName.ReverseFind( '/' ); + } + if ( substr != 0 ) { + std::size_t nSlash = substr - strPathName.GetBuffer(); + strPath = strPathName.Left( nSlash + 1 ); + strFilename = strPathName.Right( strPathName.GetLength() - nSlash - 1 ); + } + else{ + strFilename = pPath; + } + return true; +} + + + +#endif diff --git a/libs/stream/CMakeLists.txt b/libs/stream/CMakeLists.txt new file mode 100644 index 0000000..6ce7939 --- /dev/null +++ b/libs/stream/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(stream + _.cpp + filestream.h + memstream.h + stringstream.h + textfilestream.h + textstream.h + ) diff --git a/libs/stream/_.cpp b/libs/stream/_.cpp new file mode 100644 index 0000000..e69de29 diff --git a/libs/stream/filestream.h b/libs/stream/filestream.h new file mode 100644 index 0000000..826f6b5 --- /dev/null +++ b/libs/stream/filestream.h @@ -0,0 +1,173 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_STREAM_FILESTREAM_H ) +#define INCLUDED_STREAM_FILESTREAM_H + +#include "idatastream.h" +#include +#include + +namespace FileStreamDetail +{ +inline int whence_for_seekdir( SeekableStream::seekdir direction ){ + switch ( direction ) + { + case SeekableStream::cur: + return SEEK_CUR; + case SeekableStream::end: + return SEEK_END; + default: + break; + } + return SEEK_SET; +} +} + + +/// \brief A wrapper around a file input stream opened for reading in binary mode. Similar to std::ifstream. +/// +/// - Maintains a valid file handle associated with a name passed to the constructor. +/// - Implements SeekableInputStream. +class FileInputStream : public SeekableInputStream +{ +std::FILE* m_file; +public: +FileInputStream( const char* name ){ + m_file = name[0] == '\0' ? 0 : fopen( name, "rb" ); +} +~FileInputStream(){ + if ( !failed() ) { + fclose( m_file ); + } +} + +bool failed() const { + return m_file == 0; +} + +size_type read( byte_type* buffer, size_type length ){ + return fread( buffer, 1, length, m_file ); +} + +size_type seek( size_type position ){ + return fseek( m_file, static_cast( position ), SEEK_SET ); +} +size_type seek( offset_type offset, seekdir direction ){ + return fseek( m_file, offset, FileStreamDetail::whence_for_seekdir( direction ) ); +} +size_type tell() const { + return ftell( m_file ); +} + +std::FILE* file(){ + return m_file; +} +}; + +/// \brief A wrapper around a FileInputStream limiting access. +/// +/// - Maintains an input stream. +/// - Provides input starting at an offset in the file for a limited range. +class SubFileInputStream : public InputStream +{ +FileInputStream& m_istream; +size_type m_remaining; +public: +typedef FileInputStream::position_type position_type; + +SubFileInputStream( FileInputStream& istream, position_type offset, size_type size ) + : m_istream( istream ), m_remaining( size ){ + m_istream.seek( offset ); +} + +size_type read( byte_type* buffer, size_type length ){ + size_type result = m_istream.read( buffer, std::min( length, m_remaining ) ); + m_remaining -= result; + return result; +} +}; + + +/// \brief A wrapper around a stdc file stream opened for writing in binary mode. Similar to std::ofstream.. +/// +/// - Maintains a valid file handle associated with a name passed to the constructor. +/// - Implements SeekableInputStream. +class FileOutputStream : public SeekableOutputStream +{ +std::FILE* m_file; +public: +FileOutputStream( const char* name ){ + m_file = name[0] == '\0' ? 0 : fopen( name, "wb" ); +} +~FileOutputStream(){ + if ( !failed() ) { + fclose( m_file ); + } +} + +bool failed() const { + return m_file == 0; +} + +size_type write( const byte_type* buffer, size_type length ){ + return fwrite( buffer, 1, length, m_file ); +} + +size_type seek( size_type position ){ + return fseek( m_file, static_cast( position ), SEEK_SET ); +} +size_type seek( offset_type offset, seekdir direction ){ + return fseek( m_file, offset, FileStreamDetail::whence_for_seekdir( direction ) ); +} +size_type tell() const { + return ftell( m_file ); +} +}; + +inline bool file_copy( const char* source, const char* target ){ + const std::size_t buffer_size = 1024; + unsigned char buffer[buffer_size]; + + FileInputStream sourceFile( source ); + if ( sourceFile.failed() ) { + return false; + } + FileOutputStream targetFile( target ); + if ( targetFile.failed() ) { + return false; + } + + for (;; ) + { + std::size_t size = sourceFile.read( buffer, buffer_size ); + if ( size == 0 ) { + break; + } + if ( targetFile.write( buffer, size ) != size ) { + return false; + } + } + return true; +} + + +#endif diff --git a/libs/stream/memstream.h b/libs/stream/memstream.h new file mode 100644 index 0000000..75bd23a --- /dev/null +++ b/libs/stream/memstream.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_STREAM_MEMSTREAM_H ) +#define INCLUDED_STREAM_MEMSTREAM_H + +#include "itextstream.h" +#include +#include + +class BufferOutputStream : public TextOutputStream +{ +std::vector m_buffer; +public: +std::size_t write( const char* buffer, std::size_t length ){ + m_buffer.insert( m_buffer.end(), buffer, buffer + length ); + return length; +} +const char* data() const { + return &( *m_buffer.begin() ); +} +std::size_t size() const { + return m_buffer.size(); +} +void clear(){ + std::vector empty; + std::swap( empty, m_buffer ); +} +}; + +template +inline BufferOutputStream& operator<<( BufferOutputStream& ostream, const T& t ){ + return ostream_write( ostream, t ); +} + + +class BufferInputStream : public TextInputStream +{ +const char* m_read; +const char* m_end; +public: +BufferInputStream( const char* buffer, std::size_t length ) + : m_read( buffer ), m_end( buffer + length ){ +} +std::size_t read( char* buffer, std::size_t length ){ + std::size_t count = std::min( std::size_t( m_end - m_read ), length ); + const char* end = m_read + count; + while ( m_read != end ) + { + *buffer++ = *m_read++; + } + return count; +} +}; + +#endif diff --git a/libs/stream/stringstream.h b/libs/stream/stringstream.h new file mode 100644 index 0000000..8511bd4 --- /dev/null +++ b/libs/stream/stringstream.h @@ -0,0 +1,148 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_STREAM_STRINGSTREAM_H ) +#define INCLUDED_STREAM_STRINGSTREAM_H + +#include "itextstream.h" +#include "string/string.h" +#include + + +/// \brief A wrapper around a STL vector of char. +/// Maintains a null-terminated array of char. +/// Provides a limited STL-style interface to push and pop characters at the end of the string. +class StringBuffer +{ +std::vector m_string; +public: +StringBuffer(){ + m_string.push_back( '\0' ); +} +explicit StringBuffer( std::size_t capacity ){ + m_string.reserve( capacity ); + m_string.push_back( '\0' ); +} +explicit StringBuffer( const char* string ) : m_string( string, string + string_length( string ) + 1 ){ +} + +typedef std::vector::iterator iterator; +typedef std::vector::const_iterator const_iterator; + +iterator begin(){ + return m_string.begin(); +} +const_iterator begin() const { + return m_string.begin(); +} +iterator end(){ + return m_string.end() - 1; +} +const_iterator end() const { + return m_string.end() - 1; +} + +void push_back( char c ){ + m_string.insert( end(), c ); +} +void pop_back(){ + m_string.erase( end() - 1 ); +} +void push_range( const char* first, const char* last ){ + m_string.insert( end(), first, last ); +} +void push_string( const char* string ){ + push_range( string, string + string_length( string ) ); +} +char* c_str(){ + return &( *m_string.begin() ); +} +const char* c_str() const { + return &( *m_string.begin() ); +} + +char& back(){ + return *( end() - 1 ); +} +const char& back() const { + return *( end() - 1 ); +} +bool empty() const { + return m_string.size() == 1; +} +void clear(){ + m_string.clear(); + m_string.push_back( '\0' ); +} +}; + +/// \brief A TextOutputStream which writes to a StringBuffer. +/// Similar to std::stringstream. +class StringOutputStream : public TextOutputStream +{ +StringBuffer m_string; +public: +typedef StringBuffer::iterator iterator; +typedef StringBuffer::const_iterator const_iterator; + +StringOutputStream(){ +} +explicit StringOutputStream( std::size_t capacity ) : m_string( capacity ){ +} +std::size_t write( const char* buffer, std::size_t length ){ + m_string.push_range( buffer, buffer + length ); + return length; +} + +iterator begin(){ + return m_string.begin(); +} +const_iterator begin() const { + return m_string.begin(); +} +iterator end(){ + return m_string.end(); +} +const_iterator end() const { + return m_string.end(); +} + +bool empty() const { + return m_string.empty(); +} +char* c_str(){ + return m_string.c_str(); +} +const char* c_str() const { + return m_string.c_str(); +} +void clear(){ + m_string.clear(); +} +}; + +template +inline StringOutputStream& operator<<( StringOutputStream& ostream, const T& t ){ + return ostream_write( ostream, t ); +} + + +#endif diff --git a/libs/stream/textfilestream.h b/libs/stream/textfilestream.h new file mode 100644 index 0000000..41dc938 --- /dev/null +++ b/libs/stream/textfilestream.h @@ -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 + */ + +#if !defined( INCLUDED_STREAM_TEXTFILESTREAM_H ) +#define INCLUDED_STREAM_TEXTFILESTREAM_H + +#include "itextstream.h" +#include + +/// \brief A wrapper around a file input stream opened for reading in text mode. Similar to std::ifstream. +class TextFileInputStream : public TextInputStream +{ +FILE* m_file; +public: +TextFileInputStream( const char* name ){ + m_file = name[0] == '\0' ? 0 : fopen( name, "rt" ); +} +~TextFileInputStream(){ + if ( !failed() ) { + fclose( m_file ); + } +} + +bool failed() const { + return m_file == 0; +} + +std::size_t read( char* buffer, std::size_t length ){ + return fread( buffer, 1, length, m_file ); +} +}; + +/// \brief A wrapper around a file input stream opened for writing in text mode. Similar to std::ofstream. +class TextFileOutputStream : public TextOutputStream +{ +FILE* m_file; +public: +TextFileOutputStream( const char* name ){ + m_file = name[0] == '\0' ? 0 : fopen( name, "wt" ); +} +~TextFileOutputStream(){ + if ( !failed() ) { + fclose( m_file ); + } +} + +bool failed() const { + return m_file == 0; +} + +std::size_t write( const char* buffer, std::size_t length ){ + return fwrite( buffer, 1, length, m_file ); +} +}; + +template +inline TextFileOutputStream& operator<<( TextFileOutputStream& ostream, const T& t ){ + return ostream_write( ostream, t ); +} + + +#endif diff --git a/libs/stream/textstream.h b/libs/stream/textstream.h new file mode 100644 index 0000000..25f3d1a --- /dev/null +++ b/libs/stream/textstream.h @@ -0,0 +1,475 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_STREAM_TEXTSTREAM_H ) +#define INCLUDED_STREAM_TEXTSTREAM_H + +#include "globaldefs.h" + +/// \file +/// \brief Text-output-formatting. + +#include "itextstream.h" +#include "string/string.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "generic/arrayrange.h" + +namespace TextOutputDetail +{ +inline char* write_unsigned_nonzero_decimal_backward( char* ptr, unsigned int decimal ){ + for (; decimal != 0; decimal /= 10 ) + { + *--ptr = char('0' + int(decimal % 10) ); + } + return ptr; +} + + #if GDEF_ARCH_BITS_64 +inline char* write_size_t_nonzero_decimal_backward( char* ptr, size_t decimal ){ + for (; decimal != 0; decimal /= 10 ) + { + *--ptr = char('0' + (size_t)( decimal % 10 ) ); + } + return ptr; +} + #endif + +inline char* write_signed_nonzero_decimal_backward( char* ptr, int decimal, bool show_positive ){ + const bool negative = decimal < 0 ; + ptr = write_unsigned_nonzero_decimal_backward( ptr, negative ? -decimal : decimal ); + if ( negative ) { + *--ptr = '-'; + } + else if ( show_positive ) { + *--ptr = '+'; + } + return ptr; +} + +inline char* write_unsigned_nonzero_decimal_backward( char* ptr, unsigned int decimal, bool show_positive ){ + ptr = write_unsigned_nonzero_decimal_backward( ptr, decimal ); + if ( show_positive ) { + *--ptr = '+'; + } + return ptr; +} + + #if GDEF_ARCH_BITS_64 +inline char* write_size_t_nonzero_decimal_backward( char* ptr, size_t decimal, bool show_positive ){ + ptr = write_size_t_nonzero_decimal_backward( ptr, decimal ); + if ( show_positive ) { + *--ptr = '+'; + } + return ptr; +} + #endif + +inline char* write_signed_decimal_backward( char* ptr, int decimal, bool show_positive ){ + if ( decimal == 0 ) { + *--ptr = '0'; + } + else + { + ptr = write_signed_nonzero_decimal_backward( ptr, decimal, show_positive ); + } + return ptr; +} + +inline char* write_unsigned_decimal_backward( char* ptr, unsigned int decimal, bool show_positive ){ + if ( decimal == 0 ) { + *--ptr = '0'; + } + else + { + ptr = write_unsigned_nonzero_decimal_backward( ptr, decimal, show_positive ); + } + return ptr; +} + + #if GDEF_ARCH_BITS_64 +inline char* write_size_t_decimal_backward( char* ptr, size_t decimal, bool show_positive ){ + if ( decimal == 0 ) { + *--ptr = '0'; + } + else + { + ptr = write_size_t_nonzero_decimal_backward( ptr, decimal, show_positive ); + } + return ptr; +} + #endif +} + + +#if GDEF_OS_WINDOWS +#define snprintf _snprintf +#endif + +/// \brief Writes a single character \p c to \p ostream. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, char c ){ + ostream.write( &c, 1 ); + return ostream; +} + +/// \brief Writes a double-precision floating point value \p d to \p ostream. +/// The value will be formatted either as decimal with trailing zeros removed, or with scientific 'e' notation, whichever is shorter. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const double d ){ + const std::size_t bufferSize = 16; + char buf[bufferSize]; + ostream.write( buf, snprintf( buf, bufferSize, "%g", d ) ); + return ostream; +} + +/// \brief Writes a single-precision floating point value \p f to \p ostream. +/// The value will be formatted either as decimal with trailing zeros removed, or with scientific 'e' notation, whichever is shorter. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const float f ){ + return ostream_write( ostream, static_cast( f ) ); +} + +/// \brief Writes a signed integer \p i to \p ostream in decimal form. +/// A '-' sign will be added if the value is negative. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const int i ){ + const std::size_t bufferSize = 16; +#if 1 + char buf[bufferSize]; + char* begin = TextOutputDetail::write_signed_decimal_backward( buf + bufferSize, i, false ); + ostream.write( begin, ( buf + bufferSize ) - begin ); +#else + char buf[bufferSize]; + ostream.write( buf, snprintf( buf, bufferSize, "%i", i ) ); +#endif + return ostream; +} + +typedef unsigned int Unsigned; + +/// \brief Writes an unsigned integer \p i to \p ostream in decimal form. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const Unsigned i ){ + const std::size_t bufferSize = 16; +#if 1 + char buf[bufferSize]; + char* begin = TextOutputDetail::write_unsigned_decimal_backward( buf + bufferSize, i, false ); + ostream.write( begin, ( buf + bufferSize ) - begin ); +#else + char buf[bufferSize]; + ostream.write( buf, snprintf( buf, bufferSize, "%u", i ) ); +#endif + return ostream; +} + +#if GDEF_ARCH_BITS_64 + +/// \brief Writes a size_t \p i to \p ostream in decimal form. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const size_t i ){ + // max is 18446744073709551615, buffer of 32 chars should always be enough + const std::size_t bufferSize = 32; +#if 1 + char buf[bufferSize]; + char* begin = TextOutputDetail::write_size_t_decimal_backward( buf + bufferSize, i, false ); + ostream.write( begin, ( buf + bufferSize ) - begin ); +#else + char buf[bufferSize]; + ostream.write( buf, snprintf( buf, bufferSize, "%u", i ) ); +#endif + return ostream; +} + +#endif + +/// \brief Writes a null-terminated \p string to \p ostream. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const char* string ){ + ostream.write( string, strlen( string ) ); + return ostream; +} + +class HexChar +{ +public: +char m_value; +HexChar( char value ) : m_value( value ){ +} +}; + +/// \brief Writes a single character \p c to \p ostream in hexadecimal form. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const HexChar& c ){ + const std::size_t bufferSize = 16; + char buf[bufferSize]; + ostream.write( buf, snprintf( buf, bufferSize, "%X", c.m_value & 0xFF ) ); + return ostream; +} + +class FloatFormat +{ +public: +double m_f; +int m_width; +int m_precision; +FloatFormat( double f, int width, int precision ) + : m_f( f ), m_width( width ), m_precision( precision ){ +} +}; + +/// \brief Writes a floating point value to \p ostream with a specific width and precision. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const FloatFormat& formatted ){ + const std::size_t bufferSize = 32; + char buf[bufferSize]; + ostream.write( buf, snprintf( buf, bufferSize, "%*.*lf", formatted.m_width, formatted.m_precision, formatted.m_f ) ); + return ostream; +} + +// never displays exponent, prints up to 10 decimal places +class Decimal +{ +public: +double m_f; +Decimal( double f ) : m_f( f ){ +} +}; + +/// \brief Writes a floating point value to \p ostream in decimal form with trailing zeros removed. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const Decimal& decimal ){ + const int bufferSize = 22; + char buf[bufferSize]; + std::size_t length = snprintf( buf, bufferSize, "%10.10lf", decimal.m_f ); + const char* first = buf; + for (; *first == ' '; ++first ) + { + } + const char* last = buf + length - 1; + for (; *last == '0'; --last ) + { + } + if ( *last == '.' ) { + --last; + } + ostream.write( first, last - first + 1 ); + return ostream; +} + + +/// \brief Writes a \p range of characters to \p ostream. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const StringRange& range ){ + ostream.write( range.first, range.last - range.first ); + return ostream; +} + +template +class Quoted +{ +public: +const Type& m_type; +Quoted( const Type& type ) + : m_type( type ){ +} +}; + +template +inline Quoted makeQuoted( const Type& type ){ + return Quoted( type ); +} + +/// \brief Writes any type to \p ostream with a quotation mark character before and after it. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const Quoted& quoted ){ + return ostream << '"' << quoted.m_type << '"'; +} + + +class LowerCase +{ +public: +const char* m_string; +LowerCase( const char* string ) : m_string( string ){ +} +}; + +/// \brief Writes a string to \p ostream converted to lower-case. +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const LowerCase& lower ){ + for ( const char* p = lower.m_string; *p != '\0'; ++p ) + { + ostream << static_cast( std::tolower( *p ) ); + } + return ostream; +} + + +/// \brief A wrapper for a TextInputStream optimised for reading a single character at a time. +template +class SingleCharacterInputStream +{ +TextInputStreamType& m_inputStream; +char m_buffer[SIZE]; +char* m_cur; +char* m_end; + +bool fillBuffer(){ + m_end = m_buffer + m_inputStream.read( m_buffer, SIZE ); + m_cur = m_buffer; + return m_cur != m_end; +} +public: + +SingleCharacterInputStream( TextInputStreamType& inputStream ) : m_inputStream( inputStream ), m_cur( m_buffer ), m_end( m_buffer ){ +} +bool readChar( char& c ){ + if ( m_cur == m_end && !fillBuffer() ) { + return false; + } + + c = *m_cur++; + return true; +} +}; + +/// \brief A wrapper for a TextOutputStream, optimised for writing a single character at a time. +class SingleCharacterOutputStream : public TextOutputStream +{ +enum unnamed0 { m_bufsize = 1024 }; +TextOutputStream& m_ostream; +char m_buffer[m_bufsize]; +char* m_pos; +const char* m_end; + +const char* end() const { + return m_end; +} +void reset(){ + m_pos = m_buffer; +} +void flush(){ + m_ostream.write( m_buffer, m_pos - m_buffer ); + reset(); +} +public: +SingleCharacterOutputStream( TextOutputStream& ostream ) : m_ostream( ostream ), m_pos( m_buffer ), m_end( m_buffer + m_bufsize ){ +} +~SingleCharacterOutputStream(){ + flush(); +} +void write( const char c ){ + if ( m_pos == end() ) { + flush(); + } + *m_pos++ = c; +} +std::size_t write( const char* buffer, std::size_t length ){ + const char*const end = buffer + length; + for ( const char* p = buffer; p != end; ++p ) + { + write( *p ); + } + return length; +} +}; + + +/// \brief A wrapper for a TextInputStream used for reading one text line at a time. +template +class TextLinesInputStream +{ +TextInputStreamType& m_inputStream; +char m_buffer[SIZE + 1]; +char* m_cur; +char* m_end; + +int fillBuffer(){ + m_end = m_buffer + m_inputStream.read( m_buffer, SIZE ); + m_cur = m_buffer; + m_buffer[SIZE] = '\0'; + *m_end = '\0'; + return m_end - m_cur; +} +public: + +TextLinesInputStream( TextInputStreamType& inputStream ) : m_inputStream( inputStream ), m_cur( m_buffer ), m_end( m_buffer ){ + m_buffer[0] = '\0'; +} + +CopiedString readLine(){ + std::string s; + char* m_fin; + + while ( (m_fin = strchr( m_cur, '\n' )) == 0 ) + { + s.append( m_cur, m_end - m_cur ); + if ( fillBuffer() <= 0 ) break; + } + if ( m_fin != 0 ) { + s.append( m_cur, m_fin - m_cur + 1 ); + m_cur = m_fin + 1; + } + + return CopiedString( s.c_str() ); +} +}; + + +/// \brief A wrapper for a TextOutputStream, optimised for writing a few characters at a time. +template +class BufferedTextOutputStream : public TextOutputStream +{ +TextOutputStreamType outputStream; +char m_buffer[SIZE]; +char* m_cur; + +public: +BufferedTextOutputStream( TextOutputStreamType& outputStream ) : outputStream( outputStream ), m_cur( m_buffer ){ +} +~BufferedTextOutputStream(){ + outputStream.write( m_buffer, m_cur - m_buffer ); +} +std::size_t write( const char* buffer, std::size_t length ){ + std::size_t remaining = length; + for (;; ) + { + std::size_t n = std::min( remaining, std::size_t( ( m_buffer + SIZE ) - m_cur ) ); + m_cur = std::copy( buffer, buffer + n, m_cur ); + remaining -= n; + if ( remaining == 0 ) { + return 0; + } + outputStream.write( m_buffer, SIZE ); + m_cur = m_buffer; + } +} +}; + +#endif diff --git a/libs/string/CMakeLists.txt b/libs/string/CMakeLists.txt new file mode 100644 index 0000000..4b0720a --- /dev/null +++ b/libs/string/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(string + pooledstring.cpp pooledstring.h + string.h + stringfwd.h + ) diff --git a/libs/string/pooledstring.cpp b/libs/string/pooledstring.cpp new file mode 100644 index 0000000..d438633 --- /dev/null +++ b/libs/string/pooledstring.cpp @@ -0,0 +1,25 @@ + +#include "pooledstring.h" +#include "globaldefs.h" +#include "generic/static.h" + +#if GDEF_DEBUG + +namespace ExamplePooledString +{ +void testStuff(){ + PooledString< LazyStatic > a, b; + a = "monkey"; + b = "monkey"; + a = ""; +} + +struct Always +{ + Always(){ + testStuff(); + } +} always; +} + +#endif diff --git a/libs/string/pooledstring.h b/libs/string/pooledstring.h new file mode 100644 index 0000000..a388acf --- /dev/null +++ b/libs/string/pooledstring.h @@ -0,0 +1,93 @@ + +#if !defined( INCLUDED_POOLEDSTRING_H ) +#define INCLUDED_POOLEDSTRING_H + +#include +#include "generic/static.h" +#include "string/string.h" +#include "container/hashtable.h" +#include "container/hashfunc.h" + +/// \brief The string pool class. +class StringPool : public HashTable +{ +}; + +inline void StringPool_analyse( StringPool& pool ){ + typedef std::multimap Ordered; + Ordered ordered; + std::size_t total = 0; + std::size_t pooled = 0; + for ( StringPool::iterator i = pool.begin(); i != pool.end(); ++i ) + { + std::size_t size = string_length( ( *i ).key ) + 1; + total += size * ( *i ).value; + pooled += size + 20; + ordered.insert( Ordered::value_type( ( *i ).value, ( *i ).key ) ); + } + globalOutputStream() << "total: " << Unsigned( total ) << " pooled:" << Unsigned( pooled ) << "\n"; + for ( Ordered::iterator i = ordered.begin(); i != ordered.end(); ++i ) + { + globalOutputStream() << ( *i ).second << " " << Unsigned( ( *i ).first ) << "\n"; + } +} + + +/// \brief A string which can be copied with zero memory cost and minimal runtime cost. +/// +/// \param PoolContext The string pool context to use. +template +class PooledString +{ +StringPool::iterator m_i; +static StringPool::iterator increment( StringPool::iterator i ){ + ++( *i ).value; + return i; +} +static StringPool::iterator insert( const char* string ){ + StringPool::iterator i = PoolContext::instance().find( const_cast( string ) ); + if ( i == PoolContext::instance().end() ) { + return PoolContext::instance().insert( string_clone( string ), 1 ); + } + return increment( i ); +} +static void erase( StringPool::iterator i ){ + if ( --( *i ).value == 0 ) { + char* string = ( *i ).key; + PoolContext::instance().erase( i ); + string_release( string, string_length( string ) ); + } +} +public: +PooledString() : m_i( insert( "" ) ){ +} +PooledString( const PooledString& other ) : m_i( increment( other.m_i ) ){ +} +PooledString( const char* string ) : m_i( insert( string ) ){ +} +~PooledString(){ + erase( m_i ); +} +PooledString& operator=( const PooledString& other ){ + PooledString tmp( other ); + tmp.swap( *this ); + return *this; +} +PooledString& operator=( const char* string ){ + PooledString tmp( string ); + tmp.swap( *this ); + return *this; +} +void swap( PooledString& other ){ + std::swap( m_i, other.m_i ); +} +bool operator==( const PooledString& other ) const { + return m_i == other.m_i; +} +const char* c_str() const { + return ( *m_i ).key; +} +}; + + +#endif diff --git a/libs/string/string.h b/libs/string/string.h new file mode 100644 index 0000000..ff7dc45 --- /dev/null +++ b/libs/string/string.h @@ -0,0 +1,548 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_STRING_STRING_H ) +#define INCLUDED_STRING_STRING_H + +#include "globaldefs.h" + +/// \file +/// C-style null-terminated-character-array string library. + +#include +#include +#include + +#include "memory/allocator.h" +#include "generic/arrayrange.h" + +/// \brief Returns true if \p string length is zero. +/// O(1) +inline bool string_empty( const char* string ){ + return *string == '\0'; +} + +/// \brief Returns true if \p string length is not zero. +/// O(1) +inline bool string_not_empty( const char* string ){ + return !string_empty( string ); +} + +/// \brief Returns <0 if \p string is lexicographically less than \p other. +/// Returns >0 if \p string is lexicographically greater than \p other. +/// Returns 0 if \p string is lexicographically equal to \p other. +/// O(n) +inline int string_compare( const char* string, const char* other ){ + return std::strcmp( string, other ); +} + +/// \brief Returns true if \p string is lexicographically equal to \p other. +/// O(n) +inline bool string_equal( const char* string, const char* other ){ + return string_compare( string, other ) == 0; +} + +/// \brief Returns true if [\p string, \p string + \p n) is lexicographically equal to [\p other, \p other + \p n). +/// O(n) +inline bool string_equal_n( const char* string, const char* other, std::size_t n ){ + return std::strncmp( string, other, n ) == 0; +} + +/// \brief Returns true if \p string is lexicographically less than \p other. +/// O(n) +inline bool string_less( const char* string, const char* other ){ + return string_compare( string, other ) < 0; +} + +/// \brief Returns true if \p string is lexicographically greater than \p other. +/// O(n) +inline bool string_greater( const char* string, const char* other ){ + return string_compare( string, other ) > 0; +} + +/// \brief Returns <0 if \p string is lexicographically less than \p other after converting both to lower-case. +/// Returns >0 if \p string is lexicographically greater than \p other after converting both to lower-case. +/// Returns 0 if \p string is lexicographically equal to \p other after converting both to lower-case. +/// O(n) +inline int string_compare_nocase( const char* string, const char* other ){ +#if GDEF_OS_WINDOWS + return _stricmp( string, other ); +#else + return strcasecmp( string, other ); +#endif +} + +/// \brief Returns <0 if [\p string, \p string + \p n) is lexicographically less than [\p other, \p other + \p n). +/// Returns >0 if [\p string, \p string + \p n) is lexicographically greater than [\p other, \p other + \p n). +/// Returns 0 if [\p string, \p string + \p n) is lexicographically equal to [\p other, \p other + \p n). +/// Treats all ascii characters as lower-case during comparisons. +/// O(n) +inline int string_compare_nocase_n( const char* string, const char* other, std::size_t n ){ +#if GDEF_OS_WINDOWS + return _strnicmp( string, other, n ); +#else + return strncasecmp( string, other, n ); +#endif +} + +/// \brief Returns true if \p string is lexicographically equal to \p other. +/// Treats all ascii characters as lower-case during comparisons. +/// O(n) +inline bool string_equal_nocase( const char* string, const char* other ){ + return string_compare_nocase( string, other ) == 0; +} + +/// \brief Returns true if [\p string, \p string + \p n) is lexicographically equal to [\p other, \p other + \p n). +/// Treats all ascii characters as lower-case during comparisons. +/// O(n) +inline bool string_equal_nocase_n( const char* string, const char* other, std::size_t n ){ + return string_compare_nocase_n( string, other, n ) == 0; +} + +/// \brief Returns true if \p string is lexicographically less than \p other. +/// Treats all ascii characters as lower-case during comparisons. +/// O(n) +inline bool string_less_nocase( const char* string, const char* other ){ + return string_compare_nocase( string, other ) < 0; +} + +/// \brief Returns true if \p string is lexicographically greater than \p other. +/// Treats all ascii characters as lower-case during comparisons. +/// O(n) +inline bool string_greater_nocase( const char* string, const char* other ){ + return string_compare_nocase( string, other ) > 0; +} + +/// \brief Returns the number of non-null characters in \p string. +/// O(n) +inline std::size_t string_length( const char* string ){ + return std::strlen( string ); +} + +/// \brief Returns true if the beginning of \p string is equal to \p prefix. +/// O(n) +inline bool string_equal_prefix( const char* string, const char* prefix ){ + return string_equal_n( string, prefix, string_length( prefix ) ); +} + +/// \brief Returns true if the ending of \p string is equal to \p suffix. +/// O(n) +inline bool string_equal_suffix( const char* string, const char* suffix){ + const char *s = string + string_length( string ) - string_length( suffix ); + return string_equal_n( s , suffix, string_length( suffix ) ); +} + +/// \brief Copies \p other into \p string and returns \p string. +/// Assumes that the space allocated for \p string is at least string_length(other) + 1. +/// O(n) +inline char* string_copy( char* string, const char* other ){ + return std::strcpy( string, other ); +} + +/// \brief Allocates a string buffer large enough to hold \p length characters, using \p allocator. +/// The returned buffer must be released with \c string_release using a matching \p allocator. +template +inline char* string_new( std::size_t length, Allocator& allocator ){ + return allocator.allocate( length + 1 ); +} + +/// \brief Deallocates the \p buffer large enough to hold \p length characters, using \p allocator. +template +inline void string_release( char* buffer, std::size_t length, Allocator& allocator ){ + allocator.deallocate( buffer, length + 1 ); +} + +/// \brief Returns a newly-allocated string which is a clone of \p other, using \p allocator. +/// The returned buffer must be released with \c string_release using a matching \p allocator. +template +inline char* string_clone( const char* other, Allocator& allocator ){ + char* copied = string_new( string_length( other ), allocator ); + std::strcpy( copied, other ); + return copied; +} + +/// \brief Returns a newly-allocated string which is a clone of [\p first, \p last), using \p allocator. +/// The returned buffer must be released with \c string_release using a matching \p allocator. +template +inline char* string_clone_range( StringRange range, Allocator& allocator ){ + std::size_t length = range.last - range.first; + char* copied = strncpy( string_new( length, allocator ), range.first, length ); + copied[length] = '\0'; + return copied; +} + +/// \brief Allocates a string buffer large enough to hold \p length characters. +/// The returned buffer must be released with \c string_release. +inline char* string_new( std::size_t length ){ + DefaultAllocator allocator; + return string_new( length, allocator ); +} + +/// \brief Allocates a new buffer large enough to hold two concatenated strings and fills it with strings. +inline char* string_new_concat( const char* a, const char* b ){ + char* str = string_new( string_length( a ) + string_length( b ) ); + strcpy( str, a ); + strcat( str, b ); + return str; +} + +/// \brief Deallocates the \p buffer large enough to hold \p length characters. +inline void string_release( char* string, std::size_t length ){ + DefaultAllocator allocator; + string_release( string, length, allocator ); +} + +/// \brief Returns a newly-allocated string which is a clone of \p other. +/// The returned buffer must be released with \c string_release. +inline char* string_clone( const char* other ){ + DefaultAllocator allocator; + return string_clone( other, allocator ); +} + +/// \brief Returns a newly-allocated string which is a clone of [\p first, \p last). +/// The returned buffer must be released with \c string_release. +inline char* string_clone_range( StringRange range ){ + DefaultAllocator allocator; + return string_clone_range( range, allocator ); +} + +typedef char* char_pointer; +/// \brief Swaps the values of \p string and \p other. +inline void string_swap( char_pointer& string, char_pointer& other ){ + std::swap( string, other ); +} + +typedef const char* char_const_pointer; +/// \brief Swaps the values of \p string and \p other. +inline void string_swap( char_const_pointer& string, char_const_pointer& other ){ + std::swap( string, other ); +} + +/// \brief Converts each character of \p string to lower-case and returns \p string. +/// O(n) +inline char* string_to_lowercase( char* string ){ + for ( char* p = string; *p != '\0'; ++p ) + { + *p = (char)std::tolower( *p ); + } + return string; +} + +/// \brief Converts each character of \p string to upper-case and returns \p string. +/// O(n) +inline char* string_to_uppercase( char* string ){ + for ( char* p = string; *p != '\0'; ++p ) + { + *p = (char)std::toupper( *p ); + } + return string; +} + +/// \brief A re-entrant string tokeniser similar to strchr. +class StringTokeniser +{ +bool istoken( char c ) const { + if ( strchr( m_delimiters, c ) != 0 ) { + return false; + } + return true; +} +const char* advance(){ + const char* token = m_pos; + bool intoken = true; + while ( !string_empty( m_pos ) ) + { + if ( !istoken( *m_pos ) ) { + *m_pos = '\0'; + intoken = false; + } + else if ( !intoken ) { + return token; + } + ++m_pos; + } + return token; +} +std::size_t m_length; +char* m_string; +char* m_pos; +const char* m_delimiters; +public: +StringTokeniser( const char* string, const char* delimiters = " \n\r\t\v" ) : + m_length( string_length( string ) ), + m_string( string_copy( string_new( m_length ), string ) ), + m_pos( m_string ), + m_delimiters( delimiters ){ + while ( !string_empty( m_pos ) && !istoken( *m_pos ) ) + { + ++m_pos; + } +} +~StringTokeniser(){ + string_release( m_string, m_length ); +} +/// \brief Returns the next token or "" if there are no more tokens available. +const char* getToken(){ + return advance(); +} +}; + +/// \brief A non-mutable c-style string. +/// +/// \param Buffer The string storage implementation. Must be DefaultConstructible, CopyConstructible and Assignable. Must implement: +/// \li Buffer(const char* string) - constructor which copies a c-style \p string. +/// \li Buffer(const char* first, const char*) - constructor which copies a c-style string range [\p first, \p last). +/// \li void swap(Buffer& other) - swaps contents with \p other. +/// \li const char* c_str() - returns the stored non-mutable c-style string. +template +class String : public Buffer +{ +public: + +String() + : Buffer(){ +} +String( const char* string ) + : Buffer( string ){ +} +String( StringRange range ) + : Buffer( range ){ +} + +String& operator=( const String& other ){ + String temp( other ); + temp.swap( *this ); + return *this; +} +String& operator=( const char* string ){ + String temp( string ); + temp.swap( *this ); + return *this; +} +String& operator=( StringRange range ){ + String temp( range ); + temp.swap( *this ); + return *this; +} + +void swap( String& other ){ + Buffer::swap( other ); +} + +bool empty() const { + return string_empty( Buffer::c_str() ); +} +}; + +template +inline bool operator<( const String& self, const String& other ){ + return string_less( self.c_str(), other.c_str() ); +} + +template +inline bool operator>( const String& self, const String& other ){ + return string_greater( self.c_str(), other.c_str() ); +} + +template +inline bool operator==( const String& self, const String& other ){ + return string_equal( self.c_str(), other.c_str() ); +} + +template +inline bool operator!=( const String& self, const String& other ){ + return !string_equal( self.c_str(), other.c_str() ); +} + +template +inline bool operator==( const String& self, const char* other ){ + return string_equal( self.c_str(), other ); +} + +template +inline bool operator!=( const String& self, const char* other ){ + return !string_equal( self.c_str(), other ); +} + +namespace std +{ +/// \brief Swaps the values of \p self and \p other. +/// Overloads std::swap. +template +inline void swap( String& self, String& other ){ + self.swap( other ); +} +} + + +/// \brief A non-mutable string buffer which manages memory allocation. +template +class CopiedBuffer : private Allocator +{ +char* m_string; + +char* copy_range( StringRange range ){ + return string_clone_range( range, static_cast( *this ) ); +} +char* copy( const char* other ){ + return string_clone( other, static_cast( *this ) ); +} +void destroy( char* string ){ + string_release( string, string_length( string ), static_cast( *this ) ); +} + +protected: +~CopiedBuffer(){ + destroy( m_string ); +} +public: +CopiedBuffer() + : m_string( copy( "" ) ){ +} +explicit CopiedBuffer( const Allocator& allocator ) + : Allocator( allocator ), m_string( copy( "" ) ){ +} +CopiedBuffer( const CopiedBuffer& other ) + : Allocator( other ), m_string( copy( other.m_string ) ){ +} +CopiedBuffer( const char* string, const Allocator& allocator = Allocator() ) + : Allocator( allocator ), m_string( copy( string ) ){ +} +CopiedBuffer( StringRange range, const Allocator& allocator = Allocator() ) + : Allocator( allocator ), m_string( copy_range( range ) ){ +} +const char* c_str() const { + return m_string; +} +void swap( CopiedBuffer& other ){ + string_swap( m_string, other.m_string ); +} +}; + +/// \brief A non-mutable string which uses copy-by-value for assignment. +typedef String< CopiedBuffer< DefaultAllocator > > CopiedString; + + +/// \brief A non-mutable string buffer which uses reference-counting to avoid unnecessary allocations. +template +class SmartBuffer : private Allocator +{ +char* m_buffer; + +char* copy_range( StringRange range ){ + char* buffer = Allocator::allocate( sizeof( std::size_t ) + ( range.last - range.first ) + 1 ); + strncpy( buffer + sizeof( std::size_t ), range.first, range.last - range.first ); + buffer[sizeof( std::size_t ) + ( range.last - range.first )] = '\0'; + *reinterpret_cast( buffer ) = 0; + return buffer; +} +char* copy( const char* string ){ + char* buffer = Allocator::allocate( sizeof( std::size_t ) + string_length( string ) + 1 ); + strcpy( buffer + sizeof( std::size_t ), string ); + *reinterpret_cast( buffer ) = 0; + return buffer; +} +void destroy( char* buffer ){ + Allocator::deallocate( buffer, sizeof( std::size_t ) + string_length( c_str() ) + 1 ); +} + +void incref( char* buffer ){ + ++( *reinterpret_cast( buffer ) ); +} +void decref( char* buffer ){ + if ( --( *reinterpret_cast( buffer ) ) == 0 ) { + destroy( buffer ); + } +} + +protected: +~SmartBuffer(){ + decref( m_buffer ); +} +public: +SmartBuffer() + : m_buffer( copy( "" ) ){ + incref( m_buffer ); +} +explicit SmartBuffer( const Allocator& allocator ) + : Allocator( allocator ), m_buffer( copy( "" ) ){ + incref( m_buffer ); +} +SmartBuffer( const SmartBuffer& other ) + : Allocator( other ), m_buffer( other.m_buffer ){ + incref( m_buffer ); +} +SmartBuffer( const char* string, const Allocator& allocator = Allocator() ) + : Allocator( allocator ), m_buffer( copy( string ) ){ + incref( m_buffer ); +} +SmartBuffer( StringRange range, const Allocator& allocator = Allocator() ) + : Allocator( allocator ), m_buffer( copy_range( range ) ){ + incref( m_buffer ); +} +const char* c_str() const { + return m_buffer + sizeof( std::size_t ); +} +void swap( SmartBuffer& other ){ + string_swap( m_buffer, other.m_buffer ); +} +}; + +/// \brief A non-mutable string which uses copy-by-reference for assignment of SmartString. +typedef String< SmartBuffer< DefaultAllocator > > SmartString; + +class StringEqualNoCase +{ +public: +bool operator()( const CopiedString& key, const CopiedString& other ) const { + return string_equal_nocase( key.c_str(), other.c_str() ); +} +}; + +struct StringLessNoCase +{ + bool operator()( const CopiedString& x, const CopiedString& y ) const { + return string_less_nocase( x.c_str(), y.c_str() ); + } +}; + +struct RawStringEqual +{ + bool operator()( const char* x, const char* y ) const { + return string_equal( x, y ); + } +}; + +struct RawStringLess +{ + bool operator()( const char* x, const char* y ) const { + return string_less( x, y ); + } +}; + +struct RawStringLessNoCase +{ + bool operator()( const char* x, const char* y ) const { + return string_less_nocase( x, y ); + } +}; + +#endif diff --git a/libs/string/stringfwd.h b/libs/string/stringfwd.h new file mode 100644 index 0000000..d3d634e --- /dev/null +++ b/libs/string/stringfwd.h @@ -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 + */ + +#if !defined( INCLUDED_STRING_STRINGFWD_H ) +#define INCLUDED_STRING_STRINGFWD_H + +// forward-declaration of CopiedString + +template +class DefaultAllocator; +template +class CopiedBuffer; +template +class String; +typedef String< CopiedBuffer< DefaultAllocator > > CopiedString; + +#endif diff --git a/libs/stringio.h b/libs/stringio.h new file mode 100644 index 0000000..f68eba2 --- /dev/null +++ b/libs/stringio.h @@ -0,0 +1,374 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_STRINGIO_H ) +#define INCLUDED_STRINGIO_H + +#include +#include + +#include "generic/vector.h" +#include "iscriplib.h" +#include "string/string.h" +#include "generic/callback.h" +#include "property.h" + +inline float string_read_float( const char* string ){ + return static_cast( atof( string ) ); +} + +inline int string_read_int( const char* string ){ + return atoi( string ); +} + +inline bool char_is_whitespace( char c ){ + return c == ' ' || c == '\t'; +} + +inline const char* string_remove_whitespace( const char* string ){ + for (;; ) + { + if ( !char_is_whitespace( *string ) ) { + break; + } + ++string; + } + return string; +} + +inline const char* string_remove_zeros( const char* string ){ + for (;; ) + { + char c = *string; + if ( c != '0' ) { + break; + } + ++string; + } + return string; +} + +inline const char* string_remove_sign( const char* string ){ + if ( *string == '-' || *string == '+' ) { // signed zero - acceptable + return ++string; + } + return string; +} + +inline bool string_is_unsigned_zero( const char* string ){ + for (; *string != '\0'; ++string ) + { + if ( *string != '0' ) { + return false; + } + } + return true; +} + +inline bool string_is_signed_zero( const char* string ){ + return string_is_unsigned_zero( string_remove_sign( string ) ); +} + +//[whitespaces][+|-][nnnnn][.nnnnn][e|E[+|-]nnnn] +//(where whitespaces are any tab or space character and nnnnn may be any number of digits) +inline bool string_is_float_zero( const char* string ){ + string = string_remove_whitespace( string ); + if ( string_empty( string ) ) { + return false; + } + + string = string_remove_sign( string ); + if ( string_empty( string ) ) { + // no whole number or fraction part + return false; + } + + // whole-number part + string = string_remove_zeros( string ); + if ( string_empty( string ) ) { + // no fraction or exponent + return true; + } + if ( *string == '.' ) { + // fraction part + if ( *string++ != '0' ) { + // invalid fraction + return false; + } + string = string_remove_zeros( ++string ); + if ( string_empty( string ) ) { + // no exponent + return true; + } + } + if ( *string == 'e' || *string == 'E' ) { + // exponent part + string = string_remove_sign( ++string ); + if ( *string++ != '0' ) { + // invalid exponent + return false; + } + string = string_remove_zeros( ++string ); + if ( string_empty( string ) ) { + // no trailing whitespace + return true; + } + } + string = string_remove_whitespace( string ); + return string_empty( string ); +} + +inline double buffer_parse_floating_literal( const char*& buffer ){ + return strtod( buffer, const_cast( &buffer ) ); +} + +inline int buffer_parse_signed_decimal_integer_literal( const char*& buffer ){ + return strtol( buffer, const_cast( &buffer ), 10 ); +} + +inline int buffer_parse_unsigned_decimal_integer_literal( const char*& buffer ){ + return strtoul( buffer, const_cast( &buffer ), 10 ); +} + +// [+|-][nnnnn][.nnnnn][e|E[+|-]nnnnn] +inline bool string_parse_float( const char* string, float& f ){ + if ( string_empty( string ) ) { + return false; + } + f = float(buffer_parse_floating_literal( string ) ); + return string_empty( string ); +} + +// format same as float +inline bool string_parse_double( const char* string, double& f ){ + if ( string_empty( string ) ) { + return false; + } + f = buffer_parse_floating_literal( string ); + return string_empty( string ); +} + +// +template +inline bool string_parse_vector3( const char* string, BasicVector3& v ){ + if ( string_empty( string ) || *string == ' ' ) { + return false; + } + v[0] = float(buffer_parse_floating_literal( string ) ); + if ( *string++ != ' ' ) { + return false; + } + v[1] = float(buffer_parse_floating_literal( string ) ); + if ( *string++ != ' ' ) { + return false; + } + v[2] = float(buffer_parse_floating_literal( string ) ); + return string_empty( string ); +} + +template +inline bool string_parse_vector( const char* string, Float* first, Float* last ){ + if ( first != last && ( string_empty( string ) || *string == ' ' ) ) { + return false; + } + for (;; ) + { + *first = float(buffer_parse_floating_literal( string ) ); + if ( ++first == last ) { + return string_empty( string ); + } + if ( *string++ != ' ' ) { + return false; + } + } +} + +// decimal signed integer +inline bool string_parse_int( const char* string, int& i ){ + if ( string_empty( string ) ) { + return false; + } + i = buffer_parse_signed_decimal_integer_literal( string ); + return string_empty( string ); +} + +// decimal unsigned integer +inline bool string_parse_size( const char* string, std::size_t& i ){ + if ( string_empty( string ) ) { + return false; + } + i = buffer_parse_unsigned_decimal_integer_literal( string ); + return string_empty( string ); +} + + +#define RETURN_FALSE_IF_FAIL(expression) do { if (!(expression)) return false; } while (0) + +inline void Tokeniser_unexpectedError( Tokeniser& tokeniser, const char* token, const char* expected ){ + globalErrorStream() << Unsigned( tokeniser.getLine() ) << ":" << Unsigned( tokeniser.getColumn() ) << ": parse error at '" << ( token != 0 ? token : "#EOF" ) << "': expected '" << expected << "'\n"; +} + + +inline bool Tokeniser_getFloat( Tokeniser& tokeniser, float& f ){ + const char* token = tokeniser.getToken(); + if ( token != 0 && string_parse_float( token, f ) ) { + return true; + } + Tokeniser_unexpectedError( tokeniser, token, "#number" ); + return false; +} + +inline bool Tokeniser_getDouble( Tokeniser& tokeniser, double& f ){ + const char* token = tokeniser.getToken(); + if ( token != 0 && string_parse_double( token, f ) ) { + return true; + } + Tokeniser_unexpectedError( tokeniser, token, "#number" ); + return false; +} + +inline bool Tokeniser_getInteger( Tokeniser& tokeniser, int& i ){ + const char* token = tokeniser.getToken(); + if ( token != 0 && string_parse_int( token, i ) ) { + return true; + } + Tokeniser_unexpectedError( tokeniser, token, "#integer" ); + return false; +} + +inline bool Tokeniser_getSize( Tokeniser& tokeniser, std::size_t& i ){ + const char* token = tokeniser.getToken(); + if ( token != 0 && string_parse_size( token, i ) ) { + return true; + } + Tokeniser_unexpectedError( tokeniser, token, "#unsigned-integer" ); + return false; +} + +inline bool Tokeniser_nextTokenMatches( Tokeniser& tokeniser, const char* expected ){ + const char* token = tokeniser.getToken(); + if ( token != 0 && string_equal( token, expected ) ) { + return true; + } + tokeniser.ungetToken(); + return false; +} + +inline bool Tokeniser_parseToken( Tokeniser& tokeniser, const char* expected ){ + const char* token = tokeniser.getToken(); + if ( token != 0 && string_equal( token, expected ) ) { + return true; + } + Tokeniser_unexpectedError( tokeniser, token, expected ); + return false; +} + +inline bool Tokeniser_nextTokenIsDigit( Tokeniser& tokeniser ){ + const char* token = tokeniser.getToken(); + if ( token == 0 ) { + return false; + } + char c = *token; + tokeniser.ungetToken(); + return std::isdigit( c ) != 0; +} + +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& outputStream, const Vector3& v ){ + return outputStream << '(' << v.x() << ' ' << v.y() << ' ' << v.z() << ')'; +} + + +template<> +struct PropertyImpl { + static void Export(const bool &self, const Callback &returnz) { + returnz(self ? "true" : "false"); + } + + static void Import(bool &self, const char *value) { + self = string_equal(value, "true"); + } +}; + +template<> +struct PropertyImpl { + static void Export(const int &self, const Callback &returnz) { + char buffer[16]; + sprintf(buffer, "%d", self); + returnz(buffer); + } + + static void Import(int &self, const char *value) { + if (!string_parse_int(value, self)) { + self = 0; + } + } +}; + +template<> +struct PropertyImpl { + static void Export(const std::size_t &self, const Callback &returnz) { + char buffer[16]; + sprintf(buffer, "%u", Unsigned(self)); + returnz(buffer); + } + + static void Import(std::size_t &self, const char *value) { + int i; + if (string_parse_int(value, i) && i >= 0) { + self = i; + } else { + self = 0; + } + } +}; + +template<> +struct PropertyImpl { + static void Export(const float &self, const Callback &returnz) { + char buffer[16]; + sprintf(buffer, "%g", self); + returnz(buffer); + } + + static void Import(float &self, const char *value) { + if (!string_parse_float(value, self)) { + self = 0; + } + } +}; + +template<> +struct PropertyImpl { + static void Export(const Vector3 &self, const Callback &returnz) { + char buffer[64]; + sprintf(buffer, "%g %g %g", self[0], self[1], self[2]); + returnz(buffer); + } + + static void Import(Vector3 &self, const char *value) { + if (!string_parse_vector3(value, self)) { + self = Vector3(0, 0, 0); + } + } +}; + +#endif diff --git a/libs/texturelib.h b/libs/texturelib.h new file mode 100644 index 0000000..d9da1e8 --- /dev/null +++ b/libs/texturelib.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_TEXTURELIB_H ) +#define INCLUDED_TEXTURELIB_H + +#include "generic/vector.h" +typedef Vector3 Colour3; +typedef unsigned int GLuint; +class LoadImageCallback; + +// describes a GL texture +struct qtexture_t +{ + qtexture_t( const LoadImageCallback& load, const char* name ) : load( load ), name( name ){ + } + const LoadImageCallback& load; + const char* name; + std::size_t width, height; + GLuint texture_number; // gl bind number + Colour3 color; // for flat shade mode + int surfaceFlags, contentFlags, value; +}; + +#endif diff --git a/libs/transformlib.h b/libs/transformlib.h new file mode 100644 index 0000000..2974979 --- /dev/null +++ b/libs/transformlib.h @@ -0,0 +1,176 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_TRANSFORMLIB_H ) +#define INCLUDED_TRANSFORMLIB_H + +#include "generic/constant.h" +#include "math/matrix.h" +#include "math/quaternion.h" + + +/// \brief A transform node. +class TransformNode +{ +public: +STRING_CONSTANT( Name, "TransformNode" ); +/// \brief Returns the transform which maps the node's local-space into the local-space of its parent node. +virtual const Matrix4& localToParent() const = 0; +}; + +/// \brief A transform node which has no effect. +class IdentityTransform : public TransformNode +{ +public: +/// \brief Returns the identity matrix. +const Matrix4& localToParent() const { + return g_matrix4_identity; +} +}; + +/// \brief A transform node which stores a generic transformation matrix. +class MatrixTransform : public TransformNode +{ +Matrix4 m_localToParent; +public: +MatrixTransform() : m_localToParent( g_matrix4_identity ){ +} + +Matrix4& localToParent(){ + return m_localToParent; +} +/// \brief Returns the stored local->parent transform. +const Matrix4& localToParent() const { + return m_localToParent; +} +}; + + +#include "generic/callback.h" + +typedef Vector3 Translation; +typedef Quaternion Rotation; +typedef Vector3 Scale; + +inline Matrix4 matrix4_transform_for_components( const Translation& translation, const Rotation& rotation, const Scale& scale ){ + Matrix4 result( matrix4_rotation_for_quaternion_quantised( rotation ) ); + vector4_to_vector3( result.x() ) *= scale.x(); + vector4_to_vector3( result.y() ) *= scale.y(); + vector4_to_vector3( result.z() ) *= scale.z(); + result.tx() = translation.x(); + result.ty() = translation.y(); + result.tz() = translation.z(); + return result; +} + +typedef bool TransformModifierType; +const TransformModifierType TRANSFORM_PRIMITIVE = false; +const TransformModifierType TRANSFORM_COMPONENT = true; + +/// \brief A transformable scene-graph instance. +/// +/// A transformable instance may be translated, rotated or scaled. +/// The state of the instanced node's geometrical representation +/// will be the product of its geometry and the transforms of each +/// of its instances, applied in the order they appear in a graph +/// traversal. +/// Freezing the transform on an instance will cause its transform +/// to be permanently applied to the geometry of the node. +class Transformable +{ +public: +STRING_CONSTANT( Name, "Transformable" ); + +virtual void setType( TransformModifierType type ) = 0; +virtual void setTranslation( const Translation& value ) = 0; +virtual void setRotation( const Rotation& value ) = 0; +virtual void setScale( const Scale& value ) = 0; +virtual void freezeTransform() = 0; +}; + +const Translation c_translation_identity = Translation( 0, 0, 0 ); +const Rotation c_rotation_identity = c_quaternion_identity; +const Scale c_scale_identity = Scale( 1, 1, 1 ); + + +class TransformModifier : public Transformable +{ +Translation m_translation; +Rotation m_rotation; +Scale m_scale; +Callback m_changed; +Callback m_apply; +TransformModifierType m_type; +public: + +TransformModifier( const Callback& changed, const Callback& apply ) : + m_translation( c_translation_identity ), + m_rotation( c_quaternion_identity ), + m_scale( c_scale_identity ), + m_changed( changed ), + m_apply( apply ), + m_type( TRANSFORM_PRIMITIVE ){ +} +void setType( TransformModifierType type ){ + m_type = type; +} +TransformModifierType getType() const { + return m_type; +} +void setTranslation( const Translation& value ){ + m_translation = value; + m_changed(); +} +void setRotation( const Rotation& value ){ + m_rotation = value; + m_changed(); +} +void setScale( const Scale& value ){ + m_scale = value; + m_changed(); +} +void freezeTransform(){ + if ( m_translation != c_translation_identity + || m_rotation != c_rotation_identity + || m_scale != c_scale_identity ) { + m_apply(); + m_translation = c_translation_identity; + m_rotation = c_rotation_identity; + m_scale = c_scale_identity; + m_changed(); + } +} +const Translation& getTranslation() const { + return m_translation; +} +const Rotation& getRotation() const { + return m_rotation; +} +const Scale& getScale() const { + return m_scale; +} +Matrix4 calculateTransform() const { + return matrix4_transform_for_components( getTranslation(), getRotation(), getScale() ); +} +}; + + +#endif diff --git a/libs/traverselib.h b/libs/traverselib.h new file mode 100644 index 0000000..64df121 --- /dev/null +++ b/libs/traverselib.h @@ -0,0 +1,350 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined ( INCLUDED_TRAVERSELIB_H ) +#define INCLUDED_TRAVERSELIB_H + +#include "debugging/debugging.h" + +#include "scenelib.h" +#include "undolib.h" +#include "container/container.h" + +#include +#include +#include + +class TraversableObserverInsertOutputIterator +{ +protected: +scene::Traversable::Observer* m_observer; +public: +typedef std::output_iterator_tag iterator_category; +typedef void difference_type; +typedef void value_type; +typedef void pointer; +typedef void reference; + +TraversableObserverInsertOutputIterator( scene::Traversable::Observer* observer ) + : m_observer( observer ){ +} +TraversableObserverInsertOutputIterator& operator=( const NodeReference& node ){ + m_observer->insert( node ); + return *this; +} +TraversableObserverInsertOutputIterator& operator=( const NodeSmartReference& node ){ + m_observer->insert( node ); + return *this; +} +TraversableObserverInsertOutputIterator& operator*() { return *this; } +TraversableObserverInsertOutputIterator& operator++() { return *this; } +TraversableObserverInsertOutputIterator& operator++( int ) { return *this; } +}; + +class TraversableObserverEraseOutputIterator +{ +protected: +scene::Traversable::Observer* m_observer; +public: +typedef std::output_iterator_tag iterator_category; +typedef void difference_type; +typedef void value_type; +typedef void pointer; +typedef void reference; + +TraversableObserverEraseOutputIterator( scene::Traversable::Observer* observer ) + : m_observer( observer ){ +} +TraversableObserverEraseOutputIterator& operator=( const NodeReference& node ){ + m_observer->erase( node ); + return *this; +} +TraversableObserverEraseOutputIterator& operator=( const NodeSmartReference& node ){ + m_observer->erase( node ); + return *this; +} +TraversableObserverEraseOutputIterator& operator*() { return *this; } +TraversableObserverEraseOutputIterator& operator++() { return *this; } +TraversableObserverEraseOutputIterator& operator++( int ) { return *this; } +}; +typedef UnsortedSet UnsortedNodeSet; + +/// \brief Calls \p observer->\c insert for each node that exists only in \p other and \p observer->\c erase for each node that exists only in \p self +inline void nodeset_diff( const UnsortedNodeSet& self, const UnsortedNodeSet& other, scene::Traversable::Observer* observer ){ + std::vector sorted( self.begin(), self.end() ); + std::vector other_sorted( other.begin(), other.end() ); + + std::sort( sorted.begin(), sorted.end() ); + std::sort( other_sorted.begin(), other_sorted.end() ); + + std::set_difference( sorted.begin(), sorted.end(), other_sorted.begin(), other_sorted.end(), TraversableObserverEraseOutputIterator( observer ) ); + std::set_difference( other_sorted.begin(), other_sorted.end(), sorted.begin(), sorted.end(), TraversableObserverInsertOutputIterator( observer ) ); +} + +/// \brief A sequence of node references which notifies an observer of inserts and deletions, and uses the global undo system to provide undo for modifications. +class TraversableNodeSet : public scene::Traversable +{ +UnsortedNodeSet m_children; +UndoableObject m_undo; +Observer* m_observer; + +void copy( const TraversableNodeSet& other ){ + m_children = other.m_children; +} +void notifyInsertAll(){ + if ( m_observer ) { + for ( UnsortedNodeSet::iterator i = m_children.begin(); i != m_children.end(); ++i ) + { + m_observer->insert( *i ); + } + } +} +void notifyEraseAll(){ + if ( m_observer ) { + for ( UnsortedNodeSet::iterator i = m_children.begin(); i != m_children.end(); ++i ) + { + m_observer->erase( *i ); + } + } +} +public: +TraversableNodeSet() + : m_undo( *this ), m_observer( 0 ){ +} +TraversableNodeSet( const TraversableNodeSet& other ) + : scene::Traversable( other ), m_undo( *this ), m_observer( 0 ){ + copy( other ); + notifyInsertAll(); +} +~TraversableNodeSet(){ + notifyEraseAll(); +} +TraversableNodeSet& operator=( const TraversableNodeSet& other ){ +#if 1 // optimised change-tracking using diff algorithm + if ( m_observer ) { + nodeset_diff( m_children, other.m_children, m_observer ); + } + copy( other ); +#else + TraversableNodeSet tmp( other ); + tmp.swap( *this ); +#endif + return *this; +} +void swap( TraversableNodeSet& other ){ + std::swap( m_children, other.m_children ); + std::swap( m_observer, other.m_observer ); +} + +void attach( Observer* observer ){ + ASSERT_MESSAGE( m_observer == 0, "TraversableNodeSet::attach: observer cannot be attached" ); + m_observer = observer; + notifyInsertAll(); +} +void detach( Observer* observer ){ + ASSERT_MESSAGE( m_observer == observer, "TraversableNodeSet::detach: observer cannot be detached" ); + notifyEraseAll(); + m_observer = 0; +} +/// \brief \copydoc scene::Traversable::insert() +void insert( scene::Node& node ){ + m_undo.save(); + + ASSERT_MESSAGE( m_children.find( NodeSmartReference( node ) ) == m_children.end(), "TraversableNodeSet::insert - element already exists" ); + + m_children.insert( NodeSmartReference( node ) ); + + if ( m_observer ) { + m_observer->insert( node ); + } +} +/// \brief \copydoc scene::Traversable::erase() +void erase( scene::Node& node ){ + m_undo.save(); + + ASSERT_MESSAGE( m_children.find( NodeSmartReference( node ) ) != m_children.end(), "TraversableNodeSet::erase - failed to find element" ); + + if ( m_observer ) { + m_observer->erase( node ); + } + + m_children.erase( NodeSmartReference( node ) ); +} +/// \brief \copydoc scene::Traversable::traverse() +void traverse( const Walker& walker ){ + UnsortedNodeSet::iterator i = m_children.begin(); + while ( i != m_children.end() ) + { + // post-increment the iterator + Node_traverseSubgraph( *i++, walker ); + // the Walker can safely remove the current node from + // this container without invalidating the iterator + } +} +/// \brief \copydoc scene::Traversable::empty() +bool empty() const { + return m_children.empty(); +} + +void instanceAttach( MapFile* map ){ + m_undo.instanceAttach( map ); +} +void instanceDetach( MapFile* map ){ + m_undo.instanceDetach( map ); +} +}; + +namespace std +{ +/// \brief Swaps the values of \p self and \p other. +/// Overloads std::swap. +inline void swap( TraversableNodeSet& self, TraversableNodeSet& other ){ + self.swap( other ); +} +} + + +class TraversableNode : public scene::Traversable +{ +public: +TraversableNode() : m_node( 0 ), m_observer( 0 ){ +} + +// traverse +void attach( Observer* observer ){ + ASSERT_MESSAGE( m_observer == 0, "TraversableNode::attach - cannot attach observer" ); + m_observer = observer; + if ( m_node != 0 ) { + m_observer->insert( *m_node ); + } +} +void detach( Observer* observer ){ + ASSERT_MESSAGE( m_observer == observer, "TraversableNode::detach - cannot detach observer" ); + if ( m_node != 0 ) { + m_observer->erase( *m_node ); + } + m_observer = 0; +} +void insert( scene::Node& node ){ + ASSERT_MESSAGE( m_node == 0, "TraversableNode::insert - element already exists" ); + + m_node = &node; + node.IncRef(); + + if ( m_observer != 0 ) { + m_observer->insert( node ); + } +} +void erase( scene::Node& node ){ + ASSERT_MESSAGE( m_node == &node, "TraversableNode::erase - failed to find element" ); + + if ( m_observer != 0 ) { + m_observer->erase( node ); + } + + m_node = 0; + node.DecRef(); +} +void traverse( const scene::Traversable::Walker& walker ){ + if ( m_node != 0 ) { + Node_traverseSubgraph( *m_node, walker ); + } +} +bool empty() const { + return m_node != 0; +} + +scene::Node& get(){ + return *m_node; +} + +private: +scene::Node* m_node; +Observer* m_observer; +}; + +class TraversableObserverInsert +{ +scene::Node& node; +public: +TraversableObserverInsert( scene::Node& node ) : node( node ){ +} +void operator()( scene::Traversable::Observer& observer ) const { + observer.insert( node ); +} +}; + +class TraversableObserverErase +{ +scene::Node& node; +public: +TraversableObserverErase( scene::Node& node ) : node( node ){ +} +void operator()( scene::Traversable::Observer& observer ) const { + observer.erase( node ); +} +}; + +class TraversableObserverPairRelay : public ReferencePair, public scene::Traversable::Observer +{ +public: +void insert( scene::Node& node ){ + forEach( TraversableObserverInsert( node ) ); +} +void erase( scene::Node& node ){ + forEach( TraversableObserverErase( node ) ); +} +}; + +template +class ReferenceSet +{ +typedef UniqueSet Values; +Values m_values; +public: +void attach( Type& t ){ + m_values.insert( &t ); +} +void detach( Type& t ){ + m_values.erase( &t ); +} +template +void forEach( const Functor& functor ){ + for ( typename Values::iterator i = m_values.begin(); i != m_values.end(); ++i ) + { + functor( *( *i ) ); + } +} +}; + +class TraversableObserverRelay : public ReferenceSet, public scene::Traversable::Observer +{ +public: +void insert( scene::Node& node ){ + forEach( TraversableObserverInsert( node ) ); +} +void erase( scene::Node& node ){ + forEach( TraversableObserverErase( node ) ); +} +}; + + +#endif diff --git a/libs/typesystem.h b/libs/typesystem.h new file mode 100644 index 0000000..4ceca55 --- /dev/null +++ b/libs/typesystem.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_TYPESYSTEM_H ) +#define INCLUDED_TYPESYSTEM_H + + +#include +#include +#include "generic/callback.h" +#include "generic/static.h" + +class InitialiserList +{ +typedef std::list> Initialisers; +Initialisers m_initialisers; +mutable bool m_initialised; +public: +InitialiserList() : m_initialised( false ){ +} +void addInitialiser( const Callback& callback ){ + m_initialisers.push_back( callback ); +} +void initialise() const { + if ( !m_initialised ) { + m_initialised = true; + + for ( Initialisers::const_iterator i = m_initialisers.begin(); i != m_initialisers.end(); ++i ) + { + ( *i )( ); + } + } +} +}; + +//--Type System------------------- + +class TypeSystemInitialiser : public InitialiserList +{ +}; + +typedef SmartStatic StaticTypeSystemInitialiser; + +class TypeSystemRef : public StaticTypeSystemInitialiser +{ +public: +TypeSystemRef(){ + StaticTypeSystemInitialiser::instance().initialise(); +} +}; + + + +typedef std::size_t TypeId; +typedef void*( *TypeCast )( void* ); + +template +class TypeCastTable +{ +TypeCast m_casts[SIZE]; +public: +TypeCastTable(){ + std::uninitialized_fill( m_casts, m_casts + SIZE, TypeCast( 0 ) ); +} +void install( TypeId typeId, TypeCast typeCast ){ + m_casts[typeId] = typeCast; +} +void* cast( TypeId typeId, void* p ){ + TypeCast typeCast = m_casts[typeId]; + if ( typeCast != 0 ) { + return typeCast( p ); + } + return 0; +} +}; + +template +class CastInstaller +{ +public: +static void install( TypeCastTable& table ){ + table.install( Type::getTypeId(), Cast::cast ); +} +}; + +template +class IdentityCast +{ +public: +static void* cast( void* p ){ + return p; +} +}; + +template +class StaticCast +{ +public: +static void* cast( void* p ){ + return static_cast( reinterpret_cast( p ) ); +} +}; + +template +class NullType +{ +}; + +template +class ContainedCast +{ +public: +static void* cast( void* p ){ + return &reinterpret_cast( p )->get( NullType() ); +} +}; + + +#endif diff --git a/libs/uilib/CMakeLists.txt b/libs/uilib/CMakeLists.txt new file mode 100644 index 0000000..080376b --- /dev/null +++ b/libs/uilib/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(uilib + uilib.cpp + ) + +find_package(GLIB REQUIRED) +target_include_directories(uilib PUBLIC ${GLIB_INCLUDE_DIRS}) +target_link_libraries(uilib PUBLIC ${GLIB_LIBRARIES}) + +find_package(Pango REQUIRED) +target_include_directories(uilib PUBLIC ${Pango_INCLUDE_DIRS} ${PangoFT2_INCLUDE_DIRS}) +target_link_libraries(uilib PUBLIC ${Pango_LIBRARIES} ${PangoFT2_LIBRARIES}) + +find_package(GTK${GTK_TARGET} REQUIRED) +target_include_directories(uilib PUBLIC ${GTK${GTK_TARGET}_INCLUDE_DIRS}) +target_link_libraries(uilib PUBLIC ${GTK${GTK_TARGET}_LIBRARIES}) + +target_include_directories(uilib PUBLIC gtkutil) +target_link_libraries(uilib PUBLIC gtkutil) diff --git a/libs/uilib/uilib.cpp b/libs/uilib/uilib.cpp new file mode 100644 index 0000000..b423a11 --- /dev/null +++ b/libs/uilib/uilib.cpp @@ -0,0 +1,473 @@ +#include "uilib.h" + +#include + +#include + +#include "gtkutil/dialog.h" +#include "gtkutil/filechooser.h" +#include "gtkutil/messagebox.h" +#include "gtkutil/window.h" + +namespace ui { + + bool init(int *argc, char **argv[], char const *parameter_string, char const **error) + { + gtk_disable_setlocale(); + static GOptionEntry entries[] = {{}}; + char const *translation_domain = NULL; + GError *gerror = NULL; + bool ret = gtk_init_with_args(argc, argv, parameter_string, entries, translation_domain, &gerror) != 0; + if (!ret) { + *error = gerror->message; + } + return ret; + } + + void main() + { + gtk_main(); + } + + void process() + { + while (gtk_events_pending()) { + gtk_main_iteration(); + } + } + +#define IMPL(T, F) template<> _IMPL(T, F) +#define _IMPL(T, F) struct verify { using self = T; static self test(self it) { return self::from(F(it)); } } + + template + struct verify; + + template _IMPL(T,); + + template + using pointer_remove_const = std::add_pointer< + typename std::remove_const< + typename std::remove_pointer::type + >::type + >; + +#define this (verify::test(*static_cast(const_cast::type>(this)))) + + IMPL(Editable, GTK_EDITABLE); + + void IEditable::editable(bool value) + { + gtk_editable_set_editable(this, value); + } + + IMPL(TreeModel, GTK_TREE_MODEL); + + IMPL(Widget, GTK_WIDGET); + + Widget::Widget(ui::New_t) : Widget(nullptr) + {} + + Window IWidget::window() + { + return Window::from(gtk_widget_get_toplevel(this)); + } + + const char * + IWidget::file_dialog(bool open, const char *title, const char *path, const char *pattern, bool want_load, + bool want_import, bool want_save) + { + return ::file_dialog(this.window(), open, title, path, pattern, want_load, want_import, want_save); + } + + bool IWidget::visible() + { + return gtk_widget_get_visible(this) != 0; + } + + void IWidget::visible(bool shown) + { + if (shown) { + this.show(); + } else { + this.hide(); + } + } + + void IWidget::show() + { + gtk_widget_show(this); + } + + void IWidget::hide() + { + gtk_widget_hide(this); + } + + Dimensions IWidget::dimensions() + { + GtkAllocation allocation; + gtk_widget_get_allocation(this, &allocation); + return Dimensions{allocation.width, allocation.height}; + } + + void IWidget::dimensions(int width, int height) + { + gtk_widget_set_size_request(this, width, height); + } + + void IWidget::destroy() + { + gtk_widget_destroy(this); + } + + IMPL(Container, GTK_CONTAINER); + + void IContainer::add(Widget widget) + { + gtk_container_add(this, widget); + } + + void IContainer::remove(Widget widget) + { + gtk_container_remove(this, widget); + } + + IMPL(Bin, GTK_BIN); + + IMPL(Window, GTK_WINDOW); + + Window::Window(window_type type) : Window(GTK_WINDOW(gtk_window_new( + type == window_type::TOP ? GTK_WINDOW_TOPLEVEL : + type == window_type::POPUP ? GTK_WINDOW_POPUP : + GTK_WINDOW_TOPLEVEL + ))) + {} + + Window IWindow::create_dialog_window(const char *title, void func(), void *data, int default_w, int default_h) + { + return Window(::create_dialog_window(this, title, func, data, default_w, default_h)); + } + + Window IWindow::create_modal_dialog_window(const char *title, ModalDialog &dialog, int default_w, int default_h) + { + return Window(::create_modal_dialog_window(this, title, dialog, default_w, default_h)); + } + + Window IWindow::create_floating_window(const char *title) + { + return Window(::create_floating_window(title, this)); + } + + std::uint64_t IWindow::on_key_press(bool (*f)(Widget widget, _GdkEventKey *event, void *extra), void *extra) + { + using f_t = decltype(f); + struct user_data { + f_t f; + void *extra; + } *pass = new user_data{f, extra}; + auto dtor = [](user_data *data, GClosure *) { + delete data; + }; + auto func = [](_GtkWidget *widget, GdkEventKey *event, user_data *args) -> bool { + return args->f(Widget::from(widget), event, args->extra); + }; + auto clos = g_cclosure_new(G_CALLBACK(+func), pass, reinterpret_cast(+dtor)); + return g_signal_connect_closure(G_OBJECT(this), "key-press-event", clos, false); + } + + void IWindow::add_accel_group(AccelGroup group) + { + gtk_window_add_accel_group(this, group); + } + + IMPL(Alignment, GTK_ALIGNMENT); + + Alignment::Alignment(float xalign, float yalign, float xscale, float yscale) + : Alignment(GTK_ALIGNMENT(gtk_alignment_new(xalign, yalign, xscale, yscale))) + {} + + IMPL(Frame, GTK_FRAME); + + Frame::Frame(const char *label) : Frame(GTK_FRAME(gtk_frame_new(label))) + {} + + IMPL(Button, GTK_BUTTON); + + Button::Button(ui::New_t) : Button(GTK_BUTTON(gtk_button_new())) + {} + + Button::Button(const char *label) : Button(GTK_BUTTON(gtk_button_new_with_label(label))) + {} + + IMPL(ToggleButton, GTK_TOGGLE_BUTTON); + + bool IToggleButton::active() const + { + return gtk_toggle_button_get_active(this) != 0; + } + + void IToggleButton::active(bool value) + { + gtk_toggle_button_set_active(this, value); + } + + IMPL(CheckButton, GTK_CHECK_BUTTON); + + CheckButton::CheckButton(ui::New_t) : CheckButton(GTK_CHECK_BUTTON(gtk_check_button_new())) + {} + + CheckButton::CheckButton(const char *label) : CheckButton(GTK_CHECK_BUTTON(gtk_check_button_new_with_label(label))) + {} + + IMPL(MenuItem, GTK_MENU_ITEM); + + MenuItem::MenuItem(ui::New_t) : MenuItem(GTK_MENU_ITEM(gtk_menu_item_new())) + {} + + MenuItem::MenuItem(const char *label, bool mnemonic) : MenuItem( + GTK_MENU_ITEM((mnemonic ? gtk_menu_item_new_with_mnemonic : gtk_menu_item_new_with_label)(label))) + {} + + IMPL(TearoffMenuItem, GTK_TEAROFF_MENU_ITEM); + + TearoffMenuItem::TearoffMenuItem(ui::New_t) : TearoffMenuItem(GTK_TEAROFF_MENU_ITEM(gtk_tearoff_menu_item_new())) + {} + + IMPL(ComboBoxText, GTK_COMBO_BOX_TEXT); + + ComboBoxText::ComboBoxText(ui::New_t) : ComboBoxText(GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new_with_entry())) + {} + + IMPL(ScrolledWindow, GTK_SCROLLED_WINDOW); + + ScrolledWindow::ScrolledWindow(ui::New_t) : ScrolledWindow(GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(nullptr, nullptr))) + {} + + void IScrolledWindow::overflow(Policy x, Policy y) + { + gtk_scrolled_window_set_policy(this, static_cast(x), static_cast(y)); + } + + IMPL(Box, GTK_BOX); + + void IBox::pack_start(ui::Widget child, bool expand, bool fill, unsigned int padding) + { + gtk_box_pack_start(this, child, expand, fill, padding); + } + + void IBox::pack_end(ui::Widget child, bool expand, bool fill, unsigned int padding) + { + gtk_box_pack_end(this, child, expand, fill, padding); + } + + IMPL(VBox, GTK_VBOX); + + VBox::VBox(bool homogenous, int spacing) : VBox(GTK_VBOX(gtk_vbox_new(homogenous, spacing))) + {} + + IMPL(HBox, GTK_HBOX); + + HBox::HBox(bool homogenous, int spacing) : HBox(GTK_HBOX(gtk_hbox_new(homogenous, spacing))) + {} + + IMPL(HPaned, GTK_HPANED); + + HPaned::HPaned(ui::New_t) : HPaned(GTK_HPANED(gtk_hpaned_new())) + {} + + IMPL(VPaned, GTK_VPANED); + + VPaned::VPaned(ui::New_t) : VPaned(GTK_VPANED(gtk_vpaned_new())) + {} + + IMPL(Menu, GTK_MENU); + + Menu::Menu(ui::New_t) : Menu(GTK_MENU(gtk_menu_new())) + {} + + IMPL(Table, GTK_TABLE); + + Table::Table(std::size_t rows, std::size_t columns, bool homogenous) : Table( + GTK_TABLE(gtk_table_new(rows, columns, homogenous)) + ) + {} + + void ITable::attach(Widget child, TableAttach attach, TableAttachOptions options, TablePadding padding) { + gtk_table_attach(this, child, + attach.left, attach.right, attach.top, attach.bottom, + static_cast(options.x), static_cast(options.y), + padding.x, padding.y + ); + } + + IMPL(TextView, GTK_TEXT_VIEW); + + TextView::TextView(ui::New_t) : TextView(GTK_TEXT_VIEW(gtk_text_view_new())) + {} + + void ITextView::text(char const *str) + { + GtkTextBuffer *buffer = gtk_text_view_get_buffer(this); + gtk_text_buffer_set_text(buffer, str, -1); + } + + TreeView::TreeView(ui::New_t) : TreeView(GTK_TREE_VIEW(gtk_tree_view_new())) + {} + + TreeView::TreeView(TreeModel model) : TreeView(GTK_TREE_VIEW(gtk_tree_view_new_with_model(model))) + {} + + IMPL(Label, GTK_LABEL); + + Label::Label(const char *label) : Label(GTK_LABEL(gtk_label_new(label))) + {} + + void ILabel::text(char const *str) + { + gtk_label_set_text(this, str); + } + + IMPL(Image, GTK_IMAGE); + + Image::Image(ui::New_t) : Image(GTK_IMAGE(gtk_image_new())) + {} + + IMPL(Entry, GTK_ENTRY); + + Entry::Entry(ui::New_t) : Entry(GTK_ENTRY(gtk_entry_new())) + {} + + Entry::Entry(std::size_t max_length) : Entry(ui::New) + { + gtk_entry_set_max_length(this, static_cast(max_length)); + } + + char const *IEntry::text() + { + return gtk_entry_get_text(this); + } + + void IEntry::text(char const *str) + { + return gtk_entry_set_text(this, str); + } + + IMPL(SpinButton, GTK_SPIN_BUTTON); + + SpinButton::SpinButton(Adjustment adjustment, double climb_rate, std::size_t digits) : SpinButton( + GTK_SPIN_BUTTON(gtk_spin_button_new(adjustment, climb_rate, digits))) + {} + + IMPL(HScale, GTK_HSCALE); + + HScale::HScale(Adjustment adjustment) : HScale(GTK_HSCALE(gtk_hscale_new(adjustment))) + {} + + HScale::HScale(double min, double max, double step) : HScale(GTK_HSCALE(gtk_hscale_new_with_range(min, max, step))) + {} + + IMPL(Adjustment, GTK_ADJUSTMENT); + + Adjustment::Adjustment(double value, + double lower, double upper, + double step_increment, double page_increment, + double page_size) + : Adjustment( + GTK_ADJUSTMENT(gtk_adjustment_new(value, lower, upper, step_increment, page_increment, page_size))) + {} + + IMPL(CellRendererText, GTK_CELL_RENDERER_TEXT); + + CellRendererText::CellRendererText(ui::New_t) : CellRendererText(GTK_CELL_RENDERER_TEXT(gtk_cell_renderer_text_new())) + {} + + IMPL(TreeViewColumn, GTK_TREE_VIEW_COLUMN); + + TreeViewColumn::TreeViewColumn(const char *title, CellRenderer renderer, + std::initializer_list attributes) + : TreeViewColumn(gtk_tree_view_column_new_with_attributes(title, renderer, nullptr)) + { + for (auto &it : attributes) { + gtk_tree_view_column_add_attribute(this, renderer, it.attribute, it.column); + } + } + + IMPL(AccelGroup, GTK_ACCEL_GROUP); + + AccelGroup::AccelGroup(ui::New_t) : AccelGroup(GTK_ACCEL_GROUP(gtk_accel_group_new())) + {} + + IMPL(ListStore, GTK_LIST_STORE); + + void IListStore::clear() + { + gtk_list_store_clear(this); + } + + void IListStore::append() + { + gtk_list_store_append(this, nullptr); + } + + IMPL(TreeStore, GTK_TREE_STORE); + + // IMPL(TreePath, GTK_TREE_PATH); + + TreePath::TreePath(ui::New_t) : TreePath(gtk_tree_path_new()) + {} + + TreePath::TreePath(const char *path) : TreePath(gtk_tree_path_new_from_string(path)) + {} + + // Custom + +#if GTK_TARGET == 3 + + IMPL(GLArea, (void *)); + +#elif GTK_TARGET == 2 + + IMPL(GLArea, GTK_DRAWING_AREA); + +#endif + + guint IGLArea::on_render(GCallback pFunction, void *data) + { +#if GTK_TARGET == 3 + return this.connect("render", pFunction, data); +#endif +#if GTK_TARGET == 2 + return this.connect("expose_event", pFunction, data); +#endif + } + + // global + + Window root{ui::null}; + + alert_response alert(Window parent, std::string text, std::string title, alert_type type, alert_icon icon) + { + auto ret = gtk_MessageBox(parent, text.c_str(), + title.c_str(), + type == alert_type::OK ? eMB_OK : + type == alert_type::OKCANCEL ? eMB_OKCANCEL : + type == alert_type::YESNO ? eMB_YESNO : + type == alert_type::YESNOCANCEL ? eMB_YESNOCANCEL : + type == alert_type::NOYES ? eMB_NOYES : + eMB_OK, + icon == alert_icon::Default ? eMB_ICONDEFAULT : + icon == alert_icon::Error ? eMB_ICONERROR : + icon == alert_icon::Warning ? eMB_ICONWARNING : + icon == alert_icon::Question ? eMB_ICONQUESTION : + icon == alert_icon::Asterisk ? eMB_ICONASTERISK : + eMB_ICONDEFAULT + ); + return + ret == eIDOK ? alert_response::OK : + ret == eIDCANCEL ? alert_response::CANCEL : + ret == eIDYES ? alert_response::YES : + ret == eIDNO ? alert_response::NO : + alert_response::OK; + } + +} diff --git a/libs/uilib/uilib.h b/libs/uilib/uilib.h new file mode 100644 index 0000000..0941491 --- /dev/null +++ b/libs/uilib/uilib.h @@ -0,0 +1,643 @@ +#ifndef INCLUDED_UILIB_H +#define INCLUDED_UILIB_H + +#include +#include + +struct _GdkEventKey; +struct _GtkAccelGroup; +struct _GtkAdjustment; +struct _GtkAlignment; +struct _GtkBin; +struct _GtkBox; +struct _GtkButton; +struct _GtkCellEditable; +struct _GtkCellRenderer; +struct _GtkCellRendererText; +struct _GtkCheckButton; +struct _GtkCheckMenuItem; +struct _GtkComboBox; +struct _GtkComboBoxText; +struct _GtkContainer; +struct _GtkDialog; +struct _GtkEditable; +struct _GtkEntry; +struct _GtkEntryCompletion; +struct _GtkFrame; +struct _GtkHBox; +struct _GtkHPaned; +struct _GtkHScale; +struct _GtkImage; +struct _GtkItem; +struct _GtkLabel; +struct _GtkListStore; +struct _GtkTreeIter; +struct _GtkMenu; +struct _GtkMenuBar; +struct _GtkMenuItem; +struct _GtkMenuShell; +struct _GtkMisc; +struct _GtkObject; +struct _GtkPaned; +struct _GtkRadioButton; +struct _GtkRadioMenuItem; +struct _GtkRadioToolButton; +struct _GtkRange; +struct _GtkScale; +struct _GtkScrolledWindow; +struct _GtkSpinButton; +struct _GtkTable; +struct _GtkTearoffMenuItem; +struct _GtkTextView; +struct _GtkToggleButton; +struct _GtkToggleToolButton; +struct _GtkToolbar; +struct _GtkToolButton; +struct _GtkToolItem; +struct _GtkTreeModel; +struct _GtkTreePath; +struct _GtkTreeSelection; +struct _GtkTreeStore; +struct _GtkTreeView; +struct _GtkTreeViewColumn; +struct _GtkVBox; +struct _GtkVPaned; +struct _GtkWidget; +struct _GtkWindow; +struct _GTypeInstance; + +#if GTK_TARGET == 3 +struct _GtkGLArea; +#endif + +#if GTK_TARGET == 2 +using _GtkGLArea = struct _GtkDrawingArea; +#endif + +struct ModalDialog; + +namespace ui { + + bool init(int *argc, char **argv[], char const *parameter_string, char const **error); + + void main(); + + void process(); + + enum class window_type { + TOP, + POPUP + }; + + enum class Shadow { + NONE, + IN, + OUT, + ETCHED_IN, + ETCHED_OUT + }; + + enum class Policy { + ALWAYS, + AUTOMATIC, + NEVER + }; + + namespace details { + + enum class Convert { + Implicit, Explicit + }; + + template + struct Convertible; + + template + struct Convertible { + operator T() const + { return reinterpret_cast(static_cast(this)->_handle); } + }; + + template + struct Convertible { + explicit operator T() const + { return reinterpret_cast(static_cast(this)->_handle); } + }; + + template + struct All : T ... { + All() + {}; + }; + + template + struct Mixin; + template + struct Mixin { + using type = All; + }; + template + struct Mixin { + using type = All; + }; + } + + const struct Null {} null = {}; + const struct New_t {} New = {}; + + class Object : + public details::Convertible, + public details::Convertible { + public: + using self = Object *; + using native = _GtkObject *; + native _handle; + + explicit Object(native h) : _handle(h) + {} + + explicit operator bool() const + { return _handle != nullptr; } + + explicit operator void *() const + { return _handle; } + + void unref() + { g_object_unref(_handle); } + + void ref() + { g_object_ref(_handle); } + + template + gulong connect(char const *detailed_signal, Lambda &&c_handler, void *data); + + template + gulong connect(char const *detailed_signal, Lambda &&c_handler, Object data); + }; + static_assert(sizeof(Object) == sizeof(Object::native), "object slicing"); + +#define WRAP(name, super, T, interfaces, ctors, methods) \ + class name; \ + class I##name : public details::Convertible { \ + public: \ + using self = name *; \ + methods \ + }; \ + class name : public super, public I##name, public details::Mixin::type { \ + public: \ + using self = name *; \ + using native = T *; \ + protected: \ + explicit name(native h) noexcept : super(reinterpret_cast(h)) {} \ + public: \ + explicit name(Null n) noexcept : name((native) nullptr) {} \ + explicit name(New_t); \ + static name from(native h) { return name(h); } \ + static name from(void *ptr) { return name((native) ptr); } \ + ctors \ + }; \ + inline bool operator<(name self, name other) { return self._handle < other._handle; } \ + static_assert(sizeof(name) == sizeof(super), "object slicing") + + // https://developer.gnome.org/gtk2/stable/ch01.html + + // GInterface + + WRAP(CellEditable, Object, _GtkCellEditable, (), + , + ); + + WRAP(Editable, Object, _GtkEditable, (), + , + void editable(bool value); + ); + + WRAP(TreeModel, Object, _GtkTreeModel, (), + , + ); + + // GObject + + struct Dimensions { + int width; + int height; + }; + + class Window; + WRAP(Widget, Object, _GtkWidget, (), + , + Window window(); + const char *file_dialog( + bool open, + const char *title, + const char *path = nullptr, + const char *pattern = nullptr, + bool want_load = false, + bool want_import = false, + bool want_save = false + ); + bool visible(); + void visible(bool shown); + void show(); + void hide(); + Dimensions dimensions(); + void dimensions(int width, int height); + void destroy(); + ); + + WRAP(Container, Widget, _GtkContainer, (), + , + void add(Widget widget); + + void remove(Widget widget); + + template + void foreach(Lambda &&lambda); + ); + + WRAP(Bin, Container, _GtkBin, (), + , + ); + + class AccelGroup; + WRAP(Window, Bin, _GtkWindow, (), + explicit Window(window_type type); + , + Window create_dialog_window( + const char *title, + void func(), + void *data, + int default_w = -1, + int default_h = -1 + ); + + Window create_modal_dialog_window( + const char *title, + ModalDialog &dialog, + int default_w = -1, + int default_h = -1 + ); + + Window create_floating_window(const char *title); + + std::uint64_t on_key_press( + bool (*f)(Widget widget, _GdkEventKey *event, void *extra), + void *extra = nullptr + ); + + void add_accel_group(AccelGroup group); + ); + + WRAP(Dialog, Window, _GtkDialog, (), + , + ); + + WRAP(Alignment, Bin, _GtkAlignment, (), + Alignment(float xalign, float yalign, float xscale, float yscale); + , + ); + + WRAP(Frame, Bin, _GtkFrame, (), + explicit Frame(const char *label = nullptr); + , + ); + + WRAP(Button, Bin, _GtkButton, (), + explicit Button(const char *label); + , + ); + + WRAP(ToggleButton, Button, _GtkToggleButton, (), + , + bool active() const; + void active(bool value); + ); + + WRAP(CheckButton, ToggleButton, _GtkCheckButton, (), + explicit CheckButton(const char *label); + , + ); + + WRAP(RadioButton, CheckButton, _GtkRadioButton, (), + , + ); + + WRAP(Item, Bin, _GtkItem, (), + , + ); + + WRAP(MenuItem, Item, _GtkMenuItem, (), + explicit MenuItem(const char *label, bool mnemonic = false); + , + ); + + WRAP(CheckMenuItem, MenuItem, _GtkCheckMenuItem, (), + , + ); + + WRAP(RadioMenuItem, CheckMenuItem, _GtkRadioMenuItem, (), + , + ); + + WRAP(TearoffMenuItem, MenuItem, _GtkTearoffMenuItem, (), + , + ); + + WRAP(ComboBox, Bin, _GtkComboBox, (), + , + ); + + WRAP(ComboBoxText, ComboBox, _GtkComboBoxText, (), + , + ); + + WRAP(ToolItem, Bin, _GtkToolItem, (), + , + ); + + WRAP(ToolButton, ToolItem, _GtkToolButton, (), + , + ); + + WRAP(ToggleToolButton, ToolButton, _GtkToggleToolButton, (), + , + ); + + WRAP(RadioToolButton, ToggleToolButton, _GtkRadioToolButton, (), + , + ); + + WRAP(ScrolledWindow, Bin, _GtkScrolledWindow, (), + , + void overflow(Policy x, Policy y); + ); + + WRAP(Box, Container, _GtkBox, (), + , + void pack_start(ui::Widget child, bool expand, bool fill, unsigned int padding); + void pack_end(ui::Widget child, bool expand, bool fill, unsigned int padding); + ); + + WRAP(VBox, Box, _GtkVBox, (), + VBox(bool homogenous, int spacing); + , + ); + + WRAP(HBox, Box, _GtkHBox, (), + HBox(bool homogenous, int spacing); + , + ); + + WRAP(Paned, Container, _GtkPaned, (), + , + ); + + WRAP(HPaned, Paned, _GtkHPaned, (), + , + ); + + WRAP(VPaned, Paned, _GtkVPaned, (), + , + ); + + WRAP(MenuShell, Container, _GtkMenuShell, (), + , + ); + + WRAP(MenuBar, MenuShell, _GtkMenuBar, (), + , + ); + + WRAP(Menu, MenuShell, _GtkMenu, (), + , + ); + + struct TableAttach { + unsigned int left, right, top, bottom; + }; + + struct TableAttachOptions { + // todo: type safety + unsigned int x, y; + }; + + struct TablePadding { + unsigned int x, y; + }; + + WRAP(Table, Container, _GtkTable, (), + Table(std::size_t rows, std::size_t columns, bool homogenous); + , + // 5 = expand | fill + void attach(Widget child, TableAttach attach, TableAttachOptions options = {5, 5}, TablePadding padding = {0, 0}); + ); + + WRAP(TextView, Container, _GtkTextView, (), + , + void text(char const *str); + ); + + WRAP(Toolbar, Container, _GtkToolbar, (), + , + ); + + class TreeModel; + WRAP(TreeView, Widget, _GtkTreeView, (), + TreeView(TreeModel model); + , + ); + + WRAP(Misc, Widget, _GtkMisc, (), + , + ); + + WRAP(Label, Widget, _GtkLabel, (), + explicit Label(const char *label); + , + void text(char const *str); + ); + + WRAP(Image, Widget, _GtkImage, (), + , + ); + + WRAP(Entry, Widget, _GtkEntry, (IEditable, ICellEditable), + explicit Entry(std::size_t max_length); + , + char const *text(); + void text(char const *str); + ); + + class Adjustment; + WRAP(SpinButton, Entry, _GtkSpinButton, (), + SpinButton(Adjustment adjustment, double climb_rate, std::size_t digits); + , + ); + + WRAP(Range, Widget, _GtkRange, (), + , + ); + + WRAP(Scale, Range, _GtkScale, (), + , + ); + + WRAP(HScale, Scale, _GtkHScale, (), + explicit HScale(Adjustment adjustment); + HScale(double min, double max, double step); + , + ); + + WRAP(Adjustment, Object, _GtkAdjustment, (), + Adjustment(double value, + double lower, double upper, + double step_increment, double page_increment, + double page_size); + , + ); + + WRAP(CellRenderer, Object, _GtkCellRenderer, (), + , + ); + + WRAP(CellRendererText, CellRenderer, _GtkCellRendererText, (), + , + ); + + struct TreeViewColumnAttribute { + const char *attribute; + int column; + }; + WRAP(TreeViewColumn, Object, _GtkTreeViewColumn, (), + TreeViewColumn(const char *title, CellRenderer renderer, std::initializer_list attributes); + , + ); + + WRAP(AccelGroup, Object, _GtkAccelGroup, (), + , + ); + + WRAP(EntryCompletion, Object, _GtkEntryCompletion, (), + , + ); + + WRAP(ListStore, Object, _GtkListStore, (ITreeModel), + , + void clear(); + + template + void append(T... args); + + void append(); + ); + + WRAP(TreeStore, Object, _GtkTreeStore, (ITreeModel), + , + ); + + WRAP(TreeSelection, Object, _GtkTreeSelection, (), + , + ); + + // GBoxed + + WRAP(TreePath, Object, _GtkTreePath, (), + explicit TreePath(const char *path); + , + ); + + // Custom + + WRAP(GLArea, Widget, _GtkGLArea, (), + , + guint on_render(GCallback pFunction, void *data); + ); + +#undef WRAP + + // global + + enum class alert_response { + OK, + CANCEL, + YES, + NO, + }; + + enum class alert_type { + OK, + OKCANCEL, + YESNO, + YESNOCANCEL, + NOYES, + }; + + enum class alert_icon { + Default, + Error, + Warning, + Question, + Asterisk, + }; + + extern class Window root; + + alert_response alert( + Window parent, + std::string text, + std::string title = "WorldSpawn", + alert_type type = alert_type::OK, + alert_icon icon = alert_icon::Default + ); + + // callbacks + + namespace { + using GtkCallback = void (*)(_GtkWidget *, void *); + extern "C" { + void gtk_container_foreach(_GtkContainer *, GtkCallback, void *); + } + } + +#define this (*static_cast(this)) + + template + gulong Object::connect(char const *detailed_signal, Lambda &&c_handler, void *data) + { + return g_signal_connect(G_OBJECT(this), detailed_signal, c_handler, data); + } + + template + gulong Object::connect(char const *detailed_signal, Lambda &&c_handler, Object data) + { + return g_signal_connect(G_OBJECT(this), detailed_signal, c_handler, (_GtkObject *) data); + } + + template + void IContainer::foreach(Lambda &&lambda) + { + GtkCallback cb = [](_GtkWidget *widget, void *data) -> void { + using Function = typename std::decay::type; + Function *f = static_cast(data); + (*f)(Widget::from(widget)); + }; + gtk_container_foreach(this, cb, &lambda); + } + + namespace { + extern "C" { + void gtk_list_store_insert_with_values(_GtkListStore *, _GtkTreeIter *, gint position, ...); + } + } + + template + void IListStore::append(T... args) { + static_assert(sizeof...(args) % 2 == 0, "received an odd number of arguments"); + gtk_list_store_insert_with_values(this, NULL, -1, args..., -1); + } + +#undef this + +} + +#endif diff --git a/libs/undolib.h b/libs/undolib.h new file mode 100644 index 0000000..06d474b --- /dev/null +++ b/libs/undolib.h @@ -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 + */ + +#if !defined ( INCLUDED_UNDOLIB_H ) +#define INCLUDED_UNDOLIB_H + +#include "iundo.h" +#include "mapfile.h" +#include "warnings.h" +#include "generic/callback.h" + +template +class BasicUndoMemento : public UndoMemento +{ +Copyable m_data; +public: +BasicUndoMemento( const Copyable& data ) + : m_data( data ){ +} + +void release(){ + delete this; +} + +const Copyable& get() const { + return m_data; +} +}; + + +template +class ObservedUndoableObject : public Undoable +{ +typedef Callback ImportCallback; + +Copyable& m_object; +ImportCallback m_importCallback; +UndoObserver* m_undoQueue; +MapFile* m_map; +public: + +ObservedUndoableObject( Copyable & object, const ImportCallback &importCallback ) + : m_object( object ), m_importCallback( importCallback ), m_undoQueue( 0 ), m_map( 0 ) +{ +} +~ObservedUndoableObject(){ +} + +MapFile* map(){ + return m_map; +} + +void instanceAttach( MapFile* map ){ + m_map = map; + m_undoQueue = GlobalUndoSystem().observer( this ); +} +void instanceDetach( MapFile* map ){ + m_map = 0; + m_undoQueue = 0; + GlobalUndoSystem().release( this ); +} + +void save(){ + if ( m_map != 0 ) { + m_map->changed(); + } + if ( m_undoQueue != 0 ) { + m_undoQueue->save( this ); + } +} + +UndoMemento* exportState() const { + return new BasicUndoMemento( m_object ); +} +void importState( const UndoMemento* state ){ + save(); + m_importCallback( ( static_cast*>( state ) )->get() ); +} +}; + +template +class UndoableObject : public Undoable +{ +Copyable& m_object; +UndoObserver* m_undoQueue; +MapFile* m_map; + +public: +UndoableObject( Copyable& object ) + : m_object( object ), m_undoQueue( 0 ), m_map( 0 ) +{} +~UndoableObject(){ +} + +void instanceAttach( MapFile* map ){ + m_map = map; + m_undoQueue = GlobalUndoSystem().observer( this ); +} +void instanceDetach( MapFile* map ){ + m_map = 0; + m_undoQueue = 0; + GlobalUndoSystem().release( this ); +} + +void save(){ + if ( m_map != 0 ) { + m_map->changed(); + } + if ( m_undoQueue != 0 ) { + m_undoQueue->save( this ); + } +} + +UndoMemento* exportState() const { + return new BasicUndoMemento( m_object ); +} +void importState( const UndoMemento* state ){ + save(); + m_object = ( static_cast*>( state ) )->get(); +} +}; + +#endif diff --git a/libs/uniquenames.h b/libs/uniquenames.h new file mode 100644 index 0000000..8ff67f1 --- /dev/null +++ b/libs/uniquenames.h @@ -0,0 +1,322 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_UNIQUENAMES_H ) +#define INCLUDED_UNIQUENAMES_H + +#include "debugging/debugging.h" +#include +#include "string/string.h" +#include "generic/static.h" + +#if 1 +class Postfix +{ +unsigned int m_value; +public: +Postfix( const char* postfix ) : m_value( atoi( postfix ) ){ +} +unsigned int number() const { + return m_value; +} +void write( char* buffer ) const { + sprintf( buffer, "%u", m_value ); +} +Postfix& operator++(){ + ++m_value; + return *this; +} +bool operator<( const Postfix& other ) const { + return m_value < other.m_value; +} +bool operator==( const Postfix& other ) const { + return m_value == other.m_value; +} +bool operator!=( const Postfix& other ) const { + return !operator==( other ); +} +}; + +#else +class Postfix +{ +std::pair m_value; +public: +Postfix( unsigned int number, unsigned int leading_zeros ) + : m_value( leading_zeros, number ){ +} +Postfix( const char* postfix ) + : m_value( number_count_leading_zeros( postfix ), atoi( postfix ) ){ +} +unsigned int number() const { + return m_value.second; +} +unsigned int leading_zeros() const { + return m_value.first; +} +void write( char* buffer ){ + for ( unsigned int count = 0; count < m_value.first; ++count, ++buffer ) + *buffer = '0'; + sprintf( buffer, "%u", m_value.second ); +} +Postfix& operator++(){ + ++m_value.second; + if ( m_value.first != 0 && m_value.second % 10 == 0 ) { + --m_value.first; + } + return *this; +} +bool operator<( const Postfix& other ) const { + return m_value < other.m_value; +} +bool operator==( const Postfix& other ) const { + return m_value == other.m_value; +} +bool operator!=( const Postfix& other ) const { + return !operator==( other ); +} +}; + +#endif + +typedef std::pair name_t; + +inline void name_write( char* buffer, name_t name ){ + strcpy( buffer, name.first.c_str() ); + name.second.write( buffer + strlen( name.first.c_str() ) ); +} + +inline name_t name_read( const char* name ){ + const char* end = name + strlen( name ); + for ( const char* p = end; end != name; --p ) + { + if ( strrchr( "1234567890", *p ) == NULL ) { + break; + } + end = p; + } + + return name_t( CopiedString( StringRange( name, end ) ), Postfix( end ) ); +} + + +class PostFixes +{ +public: +typedef std::map postfixes_t; +postfixes_t m_postfixes; + +private: +Postfix find_first_empty() const { + Postfix postfix( "1" ); + for ( postfixes_t::const_iterator i = m_postfixes.find( postfix ); i != m_postfixes.end(); ++i, ++postfix ) + { + if ( ( *i ).first != postfix ) { + break; + } + } + return postfix; +} + +public: +Postfix make_unique( Postfix postfix ) const { + postfixes_t::const_iterator i = m_postfixes.find( postfix ); + if ( i == m_postfixes.end() ) { + return postfix; + } + else + { + return find_first_empty(); + } +} + +void insert( Postfix postfix ){ + postfixes_t::iterator i = m_postfixes.find( postfix ); + if ( i == m_postfixes.end() ) { + m_postfixes.insert( postfixes_t::value_type( postfix, 1 ) ); + } + else + { + ++( *i ).second; + } +} + +void erase( Postfix postfix ){ + postfixes_t::iterator i = m_postfixes.find( postfix ); + if ( i == m_postfixes.end() ) { + // error + } + else + { + if ( --( *i ).second == 0 ) { + m_postfixes.erase( i ); + } + } +} + +bool empty() const { + return m_postfixes.empty(); +} +}; + + +class UniqueNames +{ +typedef std::map names_t; +names_t m_names; +public: +name_t make_unique( const name_t& name ) const { + char buf[80]; + name_t r( "","" ); + name_write( buf, name ); + globalErrorStream() << "find unique name for " << buf << "\n"; + globalErrorStream() << "> currently registered names:\n"; + for ( names_t::const_iterator i = m_names.begin(); i != m_names.end(); ++i ) + { + globalErrorStream() << ">> " << i->first.c_str() << ": "; + for ( PostFixes::postfixes_t::const_iterator j = i->second.m_postfixes.begin(); j != i->second.m_postfixes.end(); ++j ) + { + j->first.write( buf ); + globalErrorStream() << " '" << buf << "'"; + } + globalErrorStream() << "\n"; + } + names_t::const_iterator i = m_names.find( name.first ); + if ( i == m_names.end() ) { + r = name; + } + else + { + r = name_t( name.first, ( *i ).second.make_unique( name.second ) ); + } + name_write( buf, r ); + globalErrorStream() << "> unique name is " << buf << "\n"; + return r; +} + +void insert( const name_t& name ){ + m_names[name.first].insert( name.second ); +} + +void erase( const name_t& name ){ + names_t::iterator i = m_names.find( name.first ); + if ( i == m_names.end() ) { + ASSERT_MESSAGE( true, "erase: name not found" ); + } + else + { + ( *i ).second.erase( name.second ); + if ( ( *i ).second.empty() ) { + m_names.erase( i ); + } + } +} + +bool empty() const { + return m_names.empty(); +} +}; + + + +#if 0 + +#undef ERROR_MESSAGE +#define ERROR_MESSAGE( message ) + +class TestUniqueName +{ +void name_check_equal( const name_t& name, const char* string, unsigned int postfix ){ + ASSERT_MESSAGE( strcmp( name.first.c_str(), string ) == 0 + && name.second.number() == postfix, + "test failed!" ); +} +void test_refcount(){ + Names names; + + names.insert( name_t( "func_bleh_", "100" ) ); + names.insert( name_t( "func_bleh_", "100" ) ); + names.insert( name_t( "func_bleh_", "100" ) ); + + + names.erase( name_t( "func_bleh_", "100" ) ); + names.erase( name_t( "func_bleh_", "100" ) ); + names.erase( name_t( "func_bleh_", "100" ) ); + + ASSERT_MESSAGE( names.empty(), "test failed!" ); +} + +void test_make_unique(){ + Names names; + + { + name_t name( names.make_unique( name_t( "func_bleh_", "01" ) ) ); + name_check_equal( name, "func_bleh_", 1 ); + names.insert( name ); + } + { + name_t name( names.make_unique( name_t( "func_bleh_", "04" ) ) ); + name_check_equal( name, "func_bleh_", 4 ); + names.insert( name ); + } + { + name_t name( names.make_unique( name_t( "func_bleh_", "04" ) ) ); + name_check_equal( name, "func_bleh_", 2 ); + names.insert( name ); + } + { + name_t name( names.make_unique( name_t( "func_bleh_", "1" ) ) ); + name_check_equal( name, "func_bleh_", 3 ); + names.insert( name ); + } + { + name_t name( names.make_unique( name_t( "func_bleh_", "2" ) ) ); + name_check_equal( name, "func_bleh_", 5 ); + names.insert( name ); + } + { + name_t name( names.make_unique( name_t( "func_bleh_", "3" ) ) ); + name_check_equal( name, "func_bleh_", 6 ); + names.insert( name ); + } + + names.erase( name_t( "func_bleh_", "1" ) ); + names.erase( name_t( "func_bleh_", "2" ) ); + names.erase( name_t( "func_bleh_", "3" ) ); + names.erase( name_t( "func_bleh_", "4" ) ); + names.erase( name_t( "func_bleh_", "5" ) ); + names.erase( name_t( "func_bleh_", "6" ) ); + + ASSERT_MESSAGE( names.empty(), "test failed!" ); +} +public: +TestUniqueName(){ + test_refcount(); + test_make_unique(); +} +}; + +const TestUniqueName g_testuniquename; + +#endif + + +#endif diff --git a/libs/versionlib.h b/libs/versionlib.h new file mode 100644 index 0000000..75704cf --- /dev/null +++ b/libs/versionlib.h @@ -0,0 +1,84 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_VERSIONLIB_H ) +#define INCLUDED_VERSIONLIB_H + +#include +#include +#include + +class Version +{ +public: +int major; +int minor; +}; + +inline bool operator<( const Version& version, const Version& other ){ + return version.major < other.major || ( !( other.major < version.major ) && version.minor < other.minor ); +} + +template +TextOutputStreamType& ostream_write( TextOutputStreamType& outputStream, const Version& version ){ + return outputStream << version.major << '.' << version.minor; +} + +/// \brief Returns true if \p version (code) is compatible with \p other (data). +inline bool version_compatible( const Version& version, const Version& other ){ + return version.major == other.major // different major-versions are always incompatible + && !( version.minor < other.minor ); // data minor-version is incompatible if greater than code minor-version +} + +inline int string_range_parse_unsigned_decimal_integer( const char* first, const char* last ){ + int result = 0; + for (; first != last; ++first ) + { + result *= 10; + result += *first - '0'; + } + return result; +} + +inline Version version_parse( const char* versionString ){ + Version version; + const char* endVersion = versionString + strlen( versionString ); + + const char* endMajor = strchr( versionString, '.' ); + if ( endMajor == 0 ) { + endMajor = endVersion; + + version.minor = 0; + } + else + { + const char* endMinor = strchr( endMajor + 1, '.' ); + if ( endMinor == 0 ) { + endMinor = endVersion; + } + version.minor = string_range_parse_unsigned_decimal_integer( endMajor + 1, endMinor ); + } + version.major = string_range_parse_unsigned_decimal_integer( versionString, endMajor ); + + return version; +} + +#endif diff --git a/libs/xml/CMakeLists.txt b/libs/xml/CMakeLists.txt new file mode 100644 index 0000000..96e1e21 --- /dev/null +++ b/libs/xml/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(xmllib + ixml.h + xmlelement.h + xmlparser.h + xmltextags.cpp xmltextags.h + xmlwriter.h + ) + +find_package(GLIB REQUIRED) +target_include_directories(xmllib PUBLIC ${GLIB_INCLUDE_DIRS}) +target_link_libraries(xmllib PUBLIC ${GLIB_LIBRARIES}) + +find_package(LibXml2 REQUIRED) +target_include_directories(xmllib PUBLIC ${LIBXML2_INCLUDE_DIR}) +target_link_libraries(xmllib PUBLIC ${LIBXML2_LIBRARIES}) diff --git a/libs/xml/ixml.h b/libs/xml/ixml.h new file mode 100644 index 0000000..7b86020 --- /dev/null +++ b/libs/xml/ixml.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_XML_IXML_H ) +#define INCLUDED_XML_IXML_H + +#include "itextstream.h" +#include "generic/constant.h" + +class XMLAttrVisitor +{ +public: +virtual void visit( const char* name, const char* value ) = 0; +}; + +class XMLElement +{ +public: +virtual const char* name() const = 0; +virtual const char* attribute( const char* name ) const = 0; +virtual void forEachAttribute( XMLAttrVisitor& visitor ) const = 0; +}; + +class XMLImporter : public TextOutputStream +{ +public: +STRING_CONSTANT( Name, "XMLImporter" ); + +virtual void pushElement( const XMLElement& element ) = 0; +virtual void popElement( const char* name ) = 0; +}; + +class XMLExporter +{ +public: +STRING_CONSTANT( Name, "XMLExporter" ); + +virtual void exportXML( XMLImporter& importer ) = 0; +}; + + +#endif diff --git a/libs/xml/xmlelement.h b/libs/xml/xmlelement.h new file mode 100644 index 0000000..5bedf55 --- /dev/null +++ b/libs/xml/xmlelement.h @@ -0,0 +1,99 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_XML_XMLELEMENT_H ) +#define INCLUDED_XML_XMLELEMENT_H + +#include "xml/ixml.h" +#include "string/string.h" + +#include + +///\brief All string pointers passed to an instance of this class are not +/// copied and must stay valid for the lifetime of the instance. +class StaticElement : public XMLElement +{ +typedef std::map attrs_t; +public: +StaticElement( const char* name ) + : m_name( name ){ +} +void insertAttribute( const char* name, const char* value ){ + m_attrs.insert( attrs_t::value_type( name, value ) ); +} +const char* name() const { + return m_name; +} +const char* attribute( const char* name ) const { + attrs_t::const_iterator i = m_attrs.find( name ); + if ( i != m_attrs.end() ) { + return i->second; + } + else{ + return ""; + } +} +void forEachAttribute( XMLAttrVisitor& visitor ) const { + for ( attrs_t::const_iterator i = m_attrs.begin(); i != m_attrs.end(); ++i ) + { + visitor.visit( i->first, i->second ); + } +} +private: +const char* m_name; +attrs_t m_attrs; +}; + +///\brief All string pointers passed to an instance of this class are copied. +class DynamicElement : public XMLElement +{ +typedef std::map attrs_t; +public: +DynamicElement( const char* name ) + : m_name( name ){ +} +void insertAttribute( const char* name, const char* value ){ + m_attrs.insert( attrs_t::value_type( name, value ) ); +} +const char* name() const { + return m_name.c_str(); +} +const char* attribute( const char* name ) const { + attrs_t::const_iterator i = m_attrs.find( name ); + if ( i != m_attrs.end() ) { + return i->second.c_str(); + } + else{ + return ""; + } +} +void forEachAttribute( XMLAttrVisitor& visitor ) const { + for ( attrs_t::const_iterator i = m_attrs.begin(); i != m_attrs.end(); ++i ) + { + visitor.visit( i->first.c_str(), i->second.c_str() ); + } +} +private: +CopiedString m_name; +attrs_t m_attrs; +}; + +#endif diff --git a/libs/xml/xmlparser.h b/libs/xml/xmlparser.h new file mode 100644 index 0000000..cdfdd8f --- /dev/null +++ b/libs/xml/xmlparser.h @@ -0,0 +1,220 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_XML_XMLPARSER_H ) +#define INCLUDED_XML_XMLPARSER_H + +#include +#include +#include "ixml.h" +#include "libxml/parser.h" +#include "convert.h" + +class TextInputStream; + +class SAXElement : public XMLElement +{ +public: +SAXElement( const char* name, const char** atts ) + : m_name( name ), m_atts( atts ){ +} +const char* name() const { + return m_name; +} +const char* attribute( const char* name ) const { + if ( m_atts != 0 ) { + for ( const char** att = m_atts; *att != 0; att += 2 ) + { + if ( strcmp( *att, name ) == 0 ) { + return *( ++att ); + } + } + } + return ""; +} +void forEachAttribute( XMLAttrVisitor& visitor ) const { + if ( m_atts != 0 ) { + for ( const char** att = m_atts; *att != 0; att += 2 ) + { + visitor.visit( *att, *( att + 1 ) ); + } + } +} +private: +const char* m_name; +const char** m_atts; +}; + +#include + +class FormattedVA +{ +public: +const char* m_format; +va_list& m_arguments; +FormattedVA( const char* format, va_list& m_arguments ) + : m_format( format ), m_arguments( m_arguments ){ +} +}; + +class Formatted +{ +public: +const char* m_format; +mutable va_list m_arguments; +Formatted( const char* format, ... ) + : m_format( format ){ + va_start( m_arguments, format ); +} +~Formatted(){ + va_end( m_arguments ); +} +}; + +#ifdef _MSC_VER +#if _MSC_VER < 1400 +#define vsnprintf std::vsnprintf +#endif +#endif + +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const FormattedVA& formatted ){ + char buffer[1024]; + ostream.write( buffer, vsnprintf( buffer, 1023, formatted.m_format, formatted.m_arguments ) ); + return ostream; +} + +template +inline TextOutputStreamType& ostream_write( TextOutputStreamType& ostream, const Formatted& formatted ){ + char buffer[1024]; + ostream.write( buffer, vsnprintf( buffer, 1023, formatted.m_format, formatted.m_arguments ) ); + return ostream; +} + +class XMLSAXImporter +{ +XMLImporter& m_importer; +xmlSAXHandler m_sax; + +static void startElement( void *user_data, const xmlChar *name, const xmlChar **atts ){ + SAXElement element( reinterpret_cast( name ), reinterpret_cast( atts ) ); + reinterpret_cast( user_data )->m_importer.pushElement( element ); +} +static void endElement( void *user_data, const xmlChar *name ){ + reinterpret_cast( user_data )->m_importer.popElement( reinterpret_cast( name ) ); +} +static void characters( void *user_data, const xmlChar *ch, int len ){ + reinterpret_cast( user_data )->m_importer + << StringRange( reinterpret_cast( ch ), reinterpret_cast( ch + len ) ); +} + +static void warning( void *user_data, const char *msg, ... ){ + va_list args; + va_start( args, msg ); + globalErrorStream() << "XML WARNING: " << FormattedVA( msg, args ); + va_end( args ); +} +static void error( void *user_data, const char *msg, ... ){ + va_list args; + va_start( args, msg ); + globalErrorStream() << "XML ERROR: " << FormattedVA( msg, args ); + va_end( args ); +} + +public: +XMLSAXImporter( XMLImporter& importer ) : m_importer( importer ){ + m_sax.internalSubset = 0; + m_sax.isStandalone = 0; + m_sax.hasInternalSubset = 0; + m_sax.hasExternalSubset = 0; + m_sax.resolveEntity = 0; + m_sax.getEntity = 0; + m_sax.entityDecl = 0; + m_sax.notationDecl = 0; + m_sax.attributeDecl = 0; + m_sax.elementDecl = 0; + m_sax.unparsedEntityDecl = 0; + m_sax.setDocumentLocator = 0; + m_sax.startDocument = 0; + m_sax.endDocument = 0; + m_sax.startElement = startElement; + m_sax.endElement = endElement; + m_sax.reference = 0; + m_sax.characters = characters; + m_sax.ignorableWhitespace = 0; + m_sax.processingInstruction = 0; + m_sax.comment = 0; + m_sax.warning = warning; + m_sax.error = error; + m_sax.fatalError = 0; + m_sax.getParameterEntity = 0; + m_sax.cdataBlock = 0; + m_sax.externalSubset = 0; + m_sax.initialized = 1; +} + +xmlSAXHandler* callbacks(){ + return &m_sax; +} +void* context(){ + return this; +} +}; + +class XMLStreamParser : public XMLExporter +{ +enum unnamed0 { BUFSIZE = 1024 }; +public: +XMLStreamParser( TextInputStream& istream ) + : m_istream( istream ){ +} +virtual void exportXML( XMLImporter& importer ){ + bool wellFormed = false; + + char chars[BUFSIZE]; + std::size_t res = m_istream.read( chars, 4 ); + if ( res > 0 ) { + XMLSAXImporter sax( importer ); + + xmlParserCtxtPtr ctxt = xmlCreatePushParserCtxt( sax.callbacks(), sax.context(), chars, static_cast( res ), 0 ); + ctxt->replaceEntities = 1; + + while ( ( res = m_istream.read( chars, BUFSIZE ) ) > 0 ) + { + xmlParseChunk( ctxt, chars, static_cast( res ), 0 ); + } + xmlParseChunk( ctxt, chars, 0, 1 ); + + wellFormed = ( ctxt->wellFormed == 1 ); + + xmlFreeParserCtxt( ctxt ); + } + + (void) wellFormed; + //return wellFormed; +} +private: +TextInputStream& m_istream; +}; + + + +#endif diff --git a/libs/xml/xmltextags.cpp b/libs/xml/xmltextags.cpp new file mode 100644 index 0000000..00f57a0 --- /dev/null +++ b/libs/xml/xmltextags.cpp @@ -0,0 +1,592 @@ +/* + 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 "xmltextags.h" + +#include + +#include "qerplugin.h" +#include "stream/stringstream.h" + +XmlTagBuilder::XmlTagBuilder(){ +} + +XmlTagBuilder::~XmlTagBuilder(){ + // clean up + xmlFreeDoc( doc ); + xmlXPathFreeContext( context ); +} + +bool XmlTagBuilder::CreateXmlDocument(){ + /* Creates an XML file + + returns TRUE if the file was created successfully or FALSE when failed + */ + + xmlTextWriterPtr writer; + + writer = xmlNewTextWriterDoc( &doc, 0 ); + + // begin a new UTF-8 formatted xml document + xmlTextWriterStartDocument( writer, NULL, "UTF-8", NULL ); + + // create the root node with stock and custom elements + xmlTextWriterStartElement( writer, (xmlChar*)"root" ); + xmlTextWriterWriteString( writer, (xmlChar*)"\n " ); + xmlTextWriterStartElement( writer, (xmlChar*)"stock" ); + xmlTextWriterWriteString( writer, (xmlChar*)"\n " ); + xmlTextWriterEndElement( writer ); + xmlTextWriterWriteString( writer, (xmlChar*)"\n " ); + xmlTextWriterStartElement( writer, (xmlChar*)"custom" ); + xmlTextWriterWriteString( writer, (xmlChar*)"\n " ); + xmlTextWriterEndElement( writer ); + xmlTextWriterWriteString( writer, (xmlChar*)"\n" ); + xmlTextWriterEndElement( writer ); + + // end of the xml document + xmlTextWriterEndDocument( writer ); + xmlFreeTextWriter( writer ); + + if ( !doc ) { + return false; + } + else { + context = xmlXPathNewContext( doc ); + return true; + } +} + +bool XmlTagBuilder::OpenXmlDoc( const char* file, const char* savefile ){ + /* Reads a XML document from a file + + returns TRUE if the document was read successfully or FALSE when failed + */ + + if ( savefile ) { + m_savefilename = savefile; + } + else{ + m_savefilename = file; + } + + doc = xmlParseFile( file ); // TODO error checking! + + if ( !doc ) { + return false; + } + else { + context = xmlXPathNewContext( doc ); + return true; + } +} + +bool XmlTagBuilder::SaveXmlDoc( void ){ + return SaveXmlDoc( m_savefilename.c_str() ); +} + +bool XmlTagBuilder::SaveXmlDoc( const char* file ){ + /* Writes the XML document + + returns TRUE if the document was saved successfully or FALSE when saving failed + */ + + xmlSaveNoEmptyTags = 1; + + if ( xmlSaveFile( file, doc ) != -1 ) { + return true; + } + return false; +} + +bool XmlTagBuilder::AddShaderNode( const char* shader, TextureType textureType, NodeShaderType nodeShaderType ){ + /* Adds a shader node + + char* shader - the name of the shader or texture (without trailing .tga or something) + + returns TRUE if the node was added successfully or FALSE when failed + */ + + xmlNodeSetPtr nodePtr = NULL; + xmlXPathObjectPtr xpathPtr = NULL; + + switch ( textureType ) + { + case STOCK: + xpathPtr = XpathEval( "/root/stock" ); + break; + case CUSTOM: + xpathPtr = XpathEval( "/root/custom" ); + }; + + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return false; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + xmlNodePtr newnode, newtext; + xmlNodePtr nodeParent = nodePtr->nodeTab[0]; + + // create a new node and set the node attribute (shader path) + switch ( nodeShaderType ) + { + case SHADER: + newnode = xmlNewNode( NULL, (xmlChar*)"shader" ); + break; + case TEXTURE: + newnode = xmlNewNode( NULL, (xmlChar*)"texture" ); + }; + + newnode = xmlDocCopyNode( newnode, doc, 1 ); + xmlSetProp( newnode, (xmlChar*)"path", (xmlChar*)shader ); + xmlNodeSetContent( newnode, (xmlChar*)"\n " ); + + if ( nodePtr->nodeTab[0]->children->next == NULL ) { // there are no shaders yet + // add spaces + newtext = xmlNewText( (xmlChar*)" " ); + xmlAddChild( nodeParent->children, newtext ); + + // add the new node + xmlAddNextSibling( nodeParent->children, newnode ); + + // append a new line + newtext = xmlNewText( (xmlChar*)"\n " ); + xmlAddNextSibling( nodeParent->children->next, newtext ); + } + else { + // add the node + xmlAddNextSibling( nodeParent->children, newnode ); + + // append a new line and spaces + newtext = xmlNewText( (xmlChar*)"\n " ); + xmlAddNextSibling( nodeParent->children->next, newtext ); + } + + xmlXPathFreeObject( xpathPtr ); + return true; + } + else { + xmlXPathFreeObject( xpathPtr ); + return false; + } +} + +bool XmlTagBuilder::DeleteShaderNode( const char* shader ){ + /* Deletes a shader node + + char* shader - the name of the shader or texture (without trailing .tga or something) + + returns TRUE if the node was deleted successfully or FALSE when failed + */ + + char buffer[256]; + char* expression = GetTagsXpathExpression( buffer, shader, EMPTY ); + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return false; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + xmlNodePtr ptrContent = nodePtr->nodeTab[0]; + xmlNodePtr ptrWhitespace = nodePtr->nodeTab[0]->prev; + + // delete the node + xmlUnlinkNode( ptrContent ); + xmlFreeNode( ptrContent ); + + // delete leading whitespace node + xmlUnlinkNode( ptrWhitespace ); + xmlFreeNode( ptrWhitespace ); + xmlXPathFreeObject( xpathPtr ); + return true; + } + xmlXPathFreeObject( xpathPtr ); + return false; +} + +bool XmlTagBuilder::CheckShaderTag( const char* shader ){ + /* Checks whether there exists an entry for a shader/texture with at least one tag + + char* shader - the name of the shader or texture (without trailing .tga or something) + + returns TRUE if the shader is already stored in the XML tag file and has at least one tag + */ + + // build the XPath expression to search for + char buffer[256]; + strcpy( buffer, "/root/*/*[@path='" ); + strcat( buffer, shader ); + strcat( buffer, "']" ); + + char* expression = buffer; + + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return false; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + xmlXPathFreeObject( xpathPtr ); + return true; + } + else { + xmlXPathFreeObject( xpathPtr ); + return false; + } +} + +bool XmlTagBuilder::CheckShaderTag( const char* shader, const char* content ){ + /* Checks whether a tag with content already exists + + char* shader - the name of the shader or texture (without trailing .tga or something) + char* content - the node content (a tag name) + + returns TRUE if the tag with content already exists or FALSE if not + */ + + // build the XPath expression to search for + // example expression: "/stock/*[@path='textures/alpha/barb_wire'][child::tag='Alpha']"; + + char buffer[256]; + strcpy( buffer, "/root/*/*[@path='" ); + strcat( buffer, shader ); + strcat( buffer, "'][child::tag='" ); + strcat( buffer, content ); + strcat( buffer, "']" ); + + char* expression = buffer; + + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return false; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + xmlXPathFreeObject( xpathPtr ); + return true; + } + else { + xmlXPathFreeObject( xpathPtr ); + return false; + } +} + +bool XmlTagBuilder::AddShaderTag( const char* shader, const char* content, NodeTagType nodeTagType ){ + /* Adds a tag node to an existing shader/texture node if there's no tag with the same content yet + + char* shader - the name of the shader or texture (without trailing .tga or something) + char* content - the node content (a tag name) + + returns TRUE if the node was added successfully or FALSE when failed + */ + + // build the XPath expression + char buffer[256]; + char* expression = GetTagsXpathExpression( buffer, shader, EMPTY ); + + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return false; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { // node was found + xmlNodePtr newnode = xmlNewNode( NULL, (xmlChar*)"tag" ); + xmlNodePtr nodeParent = nodePtr->nodeTab[0]; + newnode = xmlDocCopyNode( newnode, doc, 1 ); + xmlNodeSetContent( newnode, (xmlChar*)content ); + + if ( nodePtr->nodeTab[0]->children->next == NULL ) { // shader node has NO children + // add spaces + xmlNodePtr newtext = xmlNewText( (xmlChar*)" " ); + xmlAddChild( nodeParent->children, newtext ); + + // add new node + xmlAddNextSibling( nodeParent->children, newnode ); + + // append a new line + spaces + newtext = xmlNewText( (xmlChar*)"\n " ); + xmlAddNextSibling( nodeParent->children->next, newtext ); + } + else { // shader node has children already - the new node will be the first sibling + xmlAddNextSibling( nodeParent->children, newnode ); + xmlNodePtr newtext = xmlNewText( (xmlChar*)"\n " ); + xmlAddNextSibling( nodeParent->children->next, newtext ); + } + xmlXPathFreeObject( xpathPtr ); + return true; + } + else { + xmlXPathFreeObject( xpathPtr ); + return false; + } +} + +//int XmlTagBuilder::RenameShaderTag(const char* oldtag, const char* newtag) +int XmlTagBuilder::RenameShaderTag( const char* oldtag, CopiedString newtag ){ + /* Replaces tag node contents + + char* oldtag - the node content that sould be changed + char* newtag - the new node content + + returns the number of renamed shaders + */ + + int num = 0; + + // build the XPath expression + char expression[256]; + strcpy( expression, "/root/*/*[child::tag='" ); + strcat( expression, oldtag ); + strcat( expression, "']/*" ); + + xmlXPathObjectPtr result = xmlXPathEvalExpression( (xmlChar*)expression, context ); + if ( !result ) { + return 0; + } + xmlNodeSetPtr nodePtr = result->nodesetval; + + for ( int i = 0; i < nodePtr->nodeNr; i++ ) + { + xmlNodePtr ptrContent = nodePtr->nodeTab[i]; + char* content = (char*)xmlNodeGetContent( ptrContent ); + + if ( strcmp( content, oldtag ) == 0 ) { // found a node with old content? + xmlNodeSetContent( ptrContent, (xmlChar*)newtag.c_str() ); + num++; + } + } + + SaveXmlDoc(); + xmlXPathFreeObject( result ); // CHANGED + return num; +} + +bool XmlTagBuilder::DeleteShaderTag( const char* shader, const char* tag ){ + /* Deletes a child node of a shader + + char* shader - the name of the shader or texture (without trailing .tga or something) + char* tag - the tag being deleted + + returns TRUE if the node was deleted successfully or FALSE when failed + */ + + char buffer[256]; + char* expression = GetTagsXpathExpression( buffer, shader, TAG ); + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return false; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + for ( int i = 0; i < nodePtr->nodeNr; i++ ) + { + xmlNodePtr ptrContent = nodePtr->nodeTab[i]; + char* content = (char*)(xmlChar*)xmlNodeGetContent( ptrContent ); + + if ( strcmp( content, tag ) == 0 ) { // find the node + xmlNodePtr ptrWhitespace = nodePtr->nodeTab[i]->prev; + // delete the node + xmlUnlinkNode( ptrContent ); + xmlFreeNode( ptrContent ); + + // delete leading whitespace node + xmlUnlinkNode( ptrWhitespace ); + xmlFreeNode( ptrWhitespace ); + xmlXPathFreeObject( xpathPtr ); + return true; + } + } + } + xmlXPathFreeObject( xpathPtr ); + return false; +} + +bool XmlTagBuilder::DeleteTag( const char* tag ){ + /* Deletes a tag from all shaders + + char* tag - the tag being deleted from all shaders + + returns TRUE if the tag was deleted successfully or FALSE when failed + */ + + char expression[256]; + strcpy( expression, "/root/*/*[child::tag='" ); + strcat( expression, tag ); + strcat( expression, "']" ); + + std::set dellist; + TagSearch( expression, dellist ); + std::set::iterator iter; + + for ( iter = dellist.begin(); iter != dellist.end(); iter++ ) + { + DeleteShaderTag( iter->c_str(), tag ); + } + SaveXmlDoc(); + + return true; +} + +void XmlTagBuilder::GetShaderTags( const char* shader, std::vector& tags ){ + /* Gets the tags from a shader + + char* shader - the name of the shader + + returns a vector containing the tags + */ + + char const *expression; + + if ( shader == NULL ) { // get all tags from all shaders + expression = "/root/*/*/tag"; + } + else { + char buffer[256]; + expression = GetTagsXpathExpression( buffer, shader, TAG ); + } + + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + for ( int i = 0; i < nodePtr->nodeNr; i++ ) + { + tags.push_back( (CopiedString)(char*)xmlNodeGetContent( nodePtr->nodeTab[i] ) ); + } + } + xmlXPathFreeObject( xpathPtr ); +} + +void XmlTagBuilder::GetUntagged( std::set& shaders ){ + /* Gets all textures and shaders listed in the xml file that don't have any tag + + returns a set containing the shaders (with path) + */ + + char const *expression = "/root/*/*[not(child::tag)]"; + + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + xmlNodePtr ptr; + + for ( int i = 0; i < nodePtr->nodeNr; i++ ) + { + ptr = nodePtr->nodeTab[i]; + shaders.insert( (char*)xmlGetProp( ptr, (xmlChar*)"path" ) ); + } + } + + xmlXPathFreeObject( xpathPtr ); +} + +void XmlTagBuilder::GetAllTags( std::set& tags ){ + /* Gets a list of all tags that are used (assigned to any shader) + + returns a set containing all used tags + */ + + char const *expression = "/root/*/*/tag"; + + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + for ( int i = 0; i < nodePtr->nodeNr; i++ ) + { + tags.insert( (CopiedString)(char*)xmlNodeGetContent( nodePtr->nodeTab[i] ) ); + } + } + + xmlXPathFreeObject( xpathPtr ); +} + +void XmlTagBuilder::TagSearch( const char* expression, std::set& paths ){ + /* Searches shaders by tags + + char* expression - the XPath expression to search + + returns a set containing the found shaders + */ + + xmlXPathObjectPtr xpathPtr = XpathEval( expression ); + xmlNodeSetPtr nodePtr; + if ( xpathPtr ) { + nodePtr = xpathPtr->nodesetval; + } + else{ + return; + } + + if ( !xmlXPathNodeSetIsEmpty( nodePtr ) ) { + xmlNodePtr ptr; + xmlChar* xmlattrib; + for ( int i = 0; i < nodePtr->nodeNr; i++ ) + { + ptr = nodePtr->nodeTab[i]; + xmlattrib = xmlGetProp( ptr, (xmlChar*)"path" ); + paths.insert( (CopiedString)(char*)xmlattrib ); + } + } + xmlXPathFreeObject( xpathPtr ); +} diff --git a/libs/xml/xmltextags.h b/libs/xml/xmltextags.h new file mode 100644 index 0000000..7a1bae6 --- /dev/null +++ b/libs/xml/xmltextags.h @@ -0,0 +1,104 @@ +/* + 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_XMLTEXTAGS_H ) +#define INCLUDED_XMLTEXTAGS_H + +#include +#include +#include + +#include "iscriplib.h" + +#include +#include + +enum NodeTagType +{ + TAG, + EMPTY +}; + +enum NodeShaderType +{ + SHADER, + TEXTURE +}; + +enum TextureType +{ + STOCK, + CUSTOM +}; + +class XmlTagBuilder +{ +private: +CopiedString m_savefilename; +xmlDocPtr doc; +xmlXPathContextPtr context; +xmlNodeSetPtr nodePtr; + +xmlXPathObjectPtr XpathEval( const char* queryString ){ + xmlChar* expression = (xmlChar*)queryString; + xmlXPathObjectPtr result = xmlXPathEvalExpression( expression, context ); + return result; +}; + +char* GetTagsXpathExpression( char* buffer, const char* shader, NodeTagType nodeTagType ){ + strcpy( buffer, "/root/*/*[@path='" ); + strcat( buffer, shader ); + + switch ( nodeTagType ) + { + case TAG: + strcat( buffer, "']/tag" ); + break; + case EMPTY: + strcat( buffer, "']" ); + }; + + return buffer; +} + +public: +XmlTagBuilder(); +~XmlTagBuilder(); + +bool CreateXmlDocument(); +bool OpenXmlDoc( const char* file, const char* savefile = 0 ); +bool SaveXmlDoc( const char* file ); +bool SaveXmlDoc( void ); +bool AddShaderNode( const char* shader, TextureType textureType, NodeShaderType nodeShaderType ); +bool DeleteShaderNode( const char* shader ); +bool CheckShaderTag( const char* shader ); +bool CheckShaderTag( const char* shader, const char* content ); +bool AddShaderTag( const char* shader, const char* content, NodeTagType nodeTagType ); +bool DeleteTag( const char* tag ); +int RenameShaderTag( const char* oldtag, CopiedString newtag ); +bool DeleteShaderTag( const char* shader, const char* tag ); +void GetShaderTags( const char* shader, std::vector& tags ); +void GetUntagged( std::set& shaders ); +void GetAllTags( std::set& tags ); +void TagSearch( const char* expression, std::set& paths ); +}; + +#endif diff --git a/libs/xml/xmlwriter.h b/libs/xml/xmlwriter.h new file mode 100644 index 0000000..f54662f --- /dev/null +++ b/libs/xml/xmlwriter.h @@ -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 + */ + +#if !defined( INCLUDED_XML_XMLWRITER_H ) +#define INCLUDED_XML_XMLWRITER_H + +#include "convert.h" +#include +#include "xml/ixml.h" + +class XMLEntityOutputStream +{ +SingleCharacterOutputStream m_ostream; +public: +XMLEntityOutputStream( TextOutputStream& ostream ) + : m_ostream( ostream ){ +} +void write( const char c ){ + m_ostream.write( c ); +} +void writeEscaped( const char c ){ + switch ( c ) + { + case '<': + write( '&' ); + write( 'l' ); + write( 't' ); + write( ';' ); + break; + case '>': + write( '&' ); + write( 'g' ); + write( 't' ); + write( ';' ); + break; + case '"': + write( '&' ); + write( 'q' ); + write( 'u' ); + write( 'o' ); + write( 't' ); + write( ';' ); + break; + case '&': + write( '&' ); + write( 'a' ); + write( 'm' ); + write( 'p' ); + write( ';' ); + break; + default: + write( c ); + break; + } +} +std::size_t write( const char* buffer, std::size_t length ){ + const char*const end = buffer + length; + for ( const char* p = buffer; p != end; ++p ) + { + writeEscaped( *p ); + } + return length; +} +}; + +template +inline XMLEntityOutputStream& operator<<( XMLEntityOutputStream& ostream, const T& t ){ + return ostream_write( ostream, t ); +} + + +class XMLStreamWriter : public XMLImporter, public XMLAttrVisitor +{ +class state_t +{ +public: +enum EState +{ + eStartElement, + eContent, +}; +state_t() + : m_state( eStartElement ) +{} +EState m_state; +}; + +XMLEntityOutputStream m_ostream; +std::vector m_elements; + +void write_cdata( const char* buffer, std::size_t length ){ + m_ostream << StringRange( buffer, buffer + length ); +} +void write_string( const char* string ){ + m_ostream << string; +} +void write_quoted_string( const char* string ){ + m_ostream.write( '"' ); + m_ostream << string; + m_ostream.write( '"' ); +} +public: +XMLStreamWriter( TextOutputStream& ostream ) + : m_ostream( ostream ){ + m_elements.push_back( state_t() ); + m_elements.back().m_state = state_t::eContent; + m_ostream.write( '<' ); + m_ostream.write( '?' ); + write_string( "xml" ); + visit( "version", "1.0" ); + m_ostream.write( '?' ); + m_ostream.write( '>' ); +} + +void pushElement( const XMLElement& element ){ + if ( m_elements.back().m_state == state_t::eStartElement ) { + m_elements.back().m_state = state_t::eContent; + m_ostream.write( '>' ); + } + + m_elements.push_back( state_t() ); + + m_ostream.write( '<' ); + write_string( element.name() ); + element.forEachAttribute( *this ); +} +void popElement( const char* name ){ + if ( m_elements.back().m_state == state_t::eStartElement ) { + m_ostream.write( '/' ); + m_ostream.write( '>' ); + m_elements.pop_back(); + } + else + { + m_ostream.write( '<' ); + m_ostream.write( '/' ); + write_string( name ); + m_ostream.write( '>' ); + m_elements.pop_back(); + } +} +std::size_t write( const char* data, std::size_t length ){ + if ( m_elements.back().m_state == state_t::eStartElement ) { + m_elements.back().m_state = state_t::eContent; + m_ostream.write( '>' ); + } + write_cdata( data, length ); + return length; +} + +void visit( const char* name, const char* value ){ + m_ostream.write( ' ' ); + write_string( name ); + m_ostream.write( '=' ); + write_quoted_string( value ); +} +}; + + +#endif diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 0000000..4efde2a --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,22 @@ +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/modules") + +add_custom_target(modules) +macro(radiant_plugin name) + message(STATUS "Found Module ${name}") + add_library(${name} MODULE ${ARGN}) + add_dependencies(modules ${name}) + copy_dlls(${name}) + install( + TARGETS ${name} + LIBRARY DESTINATION modules + ) +endmacro() + +add_subdirectory(archivezip) +add_subdirectory(entity) +add_subdirectory(image) +add_subdirectory(mapq3) +add_subdirectory(iqmmodel) +add_subdirectory(model) +add_subdirectory(shaders) +add_subdirectory(vfspk3) diff --git a/plugins/archivezip/CMakeLists.txt b/plugins/archivezip/CMakeLists.txt new file mode 100644 index 0000000..7ed025d --- /dev/null +++ b/plugins/archivezip/CMakeLists.txt @@ -0,0 +1,10 @@ +radiant_plugin(archivezip + archive.cpp archive.h + pkzip.h + plugin.cpp + zlibstream.h + ) + +find_package(ZLIB REQUIRED) +target_include_directories(archivezip PRIVATE ${ZLIB_INCLUDE_DIRS}) +target_link_libraries(archivezip PRIVATE ${ZLIB_LIBRARIES}) diff --git a/plugins/archivezip/archive.cpp b/plugins/archivezip/archive.cpp new file mode 100644 index 0000000..bbbda40 --- /dev/null +++ b/plugins/archivezip/archive.cpp @@ -0,0 +1,343 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" +#include "cmdlib.h" +#include "bytestreamutils.h" + +#include "modulesystem.h" +#include "iarchive.h" + +#include +#include "stream/filestream.h" +#include "container/array.h" +#include "archivelib.h" +#include "zlibstream.h" + +class DeflatedArchiveFile : public ArchiveFile { + CopiedString m_name; + FileInputStream m_istream; + SubFileInputStream m_substream; + DeflatedInputStream m_zipstream; + FileInputStream::size_type m_size; +public: + typedef FileInputStream::size_type size_type; + typedef FileInputStream::position_type position_type; + + DeflatedArchiveFile(const char *name, const char *archiveName, position_type position, size_type stream_size, + size_type file_size) + : m_name(name), m_istream(archiveName), m_substream(m_istream, position, stream_size), + m_zipstream(m_substream), m_size(file_size) + { + } + + void release() + { + delete this; + } + + size_type size() const + { + return m_size; + } + + const char *getName() const + { + return m_name.c_str(); + } + + InputStream &getInputStream() + { + return m_zipstream; + } +}; + +class DeflatedArchiveTextFile : public ArchiveTextFile { + CopiedString m_name; + FileInputStream m_istream; + SubFileInputStream m_substream; + DeflatedInputStream m_zipstream; + BinaryToTextInputStream m_textStream; +public: + typedef FileInputStream::size_type size_type; + typedef FileInputStream::position_type position_type; + + DeflatedArchiveTextFile(const char *name, const char *archiveName, position_type position, size_type stream_size) + : m_name(name), m_istream(archiveName), m_substream(m_istream, position, stream_size), + m_zipstream(m_substream), m_textStream(m_zipstream) + { + } + + void release() + { + delete this; + } + + TextInputStream &getInputStream() + { + return m_textStream; + } +}; + +#include "pkzip.h" + +#include +#include "string/string.h" +#include "fs_filesystem.h" + + +class ZipArchive : public Archive { + class ZipRecord { + public: + enum ECompressionMode { + eStored, + eDeflated, + }; + + ZipRecord(unsigned int position, unsigned int compressed_size, unsigned int uncompressed_size, + ECompressionMode mode) + : m_position(position), m_stream_size(compressed_size), m_file_size(uncompressed_size), m_mode(mode) + { + } + + unsigned int m_position; + unsigned int m_stream_size; + unsigned int m_file_size; + ECompressionMode m_mode; + }; + + typedef GenericFileSystem ZipFileSystem; + ZipFileSystem m_filesystem; + CopiedString m_name; + FileInputStream m_istream; + + bool read_record() + { + zip_magic magic; + istream_read_zip_magic(m_istream, magic); + if (!(magic == zip_root_dirent_magic)) { + return false; + } + zip_version version_encoder; + istream_read_zip_version(m_istream, version_encoder); + zip_version version_extract; + istream_read_zip_version(m_istream, version_extract); + //unsigned short flags = + istream_read_int16_le(m_istream); + unsigned short compression_mode = istream_read_int16_le(m_istream); + if (compression_mode != Z_DEFLATED && compression_mode != 0) { + return false; + } + zip_dostime dostime; + istream_read_zip_dostime(m_istream, dostime); + //unsigned int crc32 = + istream_read_int32_le(m_istream); + unsigned int compressed_size = istream_read_uint32_le(m_istream); + unsigned int uncompressed_size = istream_read_uint32_le(m_istream); + unsigned int namelength = istream_read_uint16_le(m_istream); + unsigned short extras = istream_read_uint16_le(m_istream); + unsigned short comment = istream_read_uint16_le(m_istream); + //unsigned short diskstart = + istream_read_int16_le(m_istream); + //unsigned short filetype = + istream_read_int16_le(m_istream); + //unsigned int filemode = + istream_read_int32_le(m_istream); + unsigned int position = istream_read_int32_le(m_istream); + + Array filename(namelength + 1); + m_istream.read(reinterpret_cast( filename.data()), namelength); + filename[namelength] = '\0'; + + m_istream.seek(extras + comment, FileInputStream::cur); + + if (path_is_directory(filename.data())) { + m_filesystem[filename.data()] = 0; + } else { + ZipFileSystem::entry_type &file = m_filesystem[filename.data()]; + if (!file.is_directory()) { + globalOutputStream() << "Warning: zip archive " << makeQuoted(m_name.c_str()) + << " contains duplicated file: " << makeQuoted(filename.data()) << "\n"; + } else { + file = new ZipRecord(position, compressed_size, uncompressed_size, + (compression_mode == Z_DEFLATED) ? ZipRecord::eDeflated : ZipRecord::eStored); + } + } + + return true; + } + + bool read_pkzip() + { + SeekableStream::position_type pos = pkzip_find_disk_trailer(m_istream); + if (pos != 0) { + zip_disk_trailer disk_trailer; + m_istream.seek(pos); + istream_read_zip_disk_trailer(m_istream, disk_trailer); + if (!(disk_trailer.z_magic == zip_disk_trailer_magic)) { + return false; + } + + m_istream.seek(disk_trailer.z_rootseek); + for (unsigned int i = 0; i < disk_trailer.z_entries; ++i) { + if (!read_record()) { + return false; + } + } + return true; + } + return false; + } + +public: + ZipArchive(const char *name) + : m_name(name), m_istream(name) + { + if (!m_istream.failed()) { + if (!read_pkzip()) { + globalErrorStream() << "ERROR: invalid zip-file " << makeQuoted(name) << '\n'; + } + } + } + + ~ZipArchive() + { + for (ZipFileSystem::iterator i = m_filesystem.begin(); i != m_filesystem.end(); ++i) { + delete i->second.file(); + } + } + + bool failed() + { + return m_istream.failed(); + } + + void release() + { + delete this; + } + + ArchiveFile *openFile(const char *name) + { + ZipFileSystem::iterator i = m_filesystem.find(name); + if (i != m_filesystem.end() && !i->second.is_directory()) { + ZipRecord *file = i->second.file(); + + m_istream.seek(file->m_position); + zip_file_header file_header; + istream_read_zip_file_header(m_istream, file_header); + if (file_header.z_magic != zip_file_header_magic) { + globalErrorStream() << "error reading zip file " << makeQuoted(m_name.c_str()); + return 0; + } + + switch (file->m_mode) { + case ZipRecord::eStored: + return StoredArchiveFile::create(name, m_name.c_str(), m_istream.tell(), file->m_stream_size, + file->m_file_size); + case ZipRecord::eDeflated: + return new DeflatedArchiveFile(name, m_name.c_str(), m_istream.tell(), file->m_stream_size, + file->m_file_size); + } + } + return 0; + } + + ArchiveTextFile *openTextFile(const char *name) + { + ZipFileSystem::iterator i = m_filesystem.find(name); + if (i != m_filesystem.end() && !i->second.is_directory()) { + ZipRecord *file = i->second.file(); + + m_istream.seek(file->m_position); + zip_file_header file_header; + istream_read_zip_file_header(m_istream, file_header); + if (file_header.z_magic != zip_file_header_magic) { + globalErrorStream() << "error reading zip file " << makeQuoted(m_name.c_str()); + return 0; + } + + switch (file->m_mode) { + case ZipRecord::eStored: + return StoredArchiveTextFile::create(name, m_name.c_str(), m_istream.tell(), file->m_stream_size); + case ZipRecord::eDeflated: + return new DeflatedArchiveTextFile(name, m_name.c_str(), m_istream.tell(), file->m_stream_size); + } + } + return 0; + } + + bool containsFile(const char *name) + { + ZipFileSystem::iterator i = m_filesystem.find(name); + return i != m_filesystem.end() && !i->second.is_directory(); + } + + void forEachFile(VisitorFunc visitor, const char *root) + { + m_filesystem.traverse(visitor, root); + } +}; + +Archive *OpenArchive(const char *name) +{ + return new ZipArchive(name); +} + +#if 0 + +class TestZip +{ +class TestVisitor : public Archive::IVisitor +{ +public: +void visit( const char* name ){ + int bleh = 0; +} +}; +public: +TestZip(){ + testzip( "c:/quake3/baseq3/mapmedia.pk3", "textures/radiant/notex.tga" ); +} + +void testzip( const char* name, const char* filename ){ + Archive* archive = OpenArchive( name ); + ArchiveFile* file = archive->openFile( filename ); + if ( file != 0 ) { + unsigned char buffer[4096]; + std::size_t count = file->getInputStream().read( (InputStream::byte_type*)buffer, 4096 ); + file->release(); + } + TestVisitor visitor; + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 0 ), "" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFiles, 1 ), "" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eDirectories, 1 ), "" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "textures" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 1 ), "textures/" ); + archive->forEachFile( Archive::VisitorFunc( &visitor, Archive::eFilesAndDirectories, 2 ), "" ); + archive->release(); +} +}; + +TestZip g_TestZip; + +#endif diff --git a/plugins/archivezip/archive.h b/plugins/archivezip/archive.h new file mode 100644 index 0000000..95f64d9 --- /dev/null +++ b/plugins/archivezip/archive.h @@ -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 + */ + +Archive *OpenArchive(const char *name); diff --git a/plugins/archivezip/archivezip.def b/plugins/archivezip/archivezip.def new file mode 100644 index 0000000..82b0c81 --- /dev/null +++ b/plugins/archivezip/archivezip.def @@ -0,0 +1,7 @@ +; archivezip.def : Declares the module parameters for the DLL. + +LIBRARY "ARCHIVEZIP" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/archivezip/pkzip.h b/plugins/archivezip/pkzip.h new file mode 100644 index 0000000..94cdf84 --- /dev/null +++ b/plugins/archivezip/pkzip.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_PKZIP_H ) +#define INCLUDED_PKZIP_H + +#include "bytestreamutils.h" +#include "idatastream.h" +#include + +class zip_magic { +public: + bool operator==(const zip_magic &other) const + { + return m_value[0] == other.m_value[0] + && m_value[1] == other.m_value[1] + && m_value[2] == other.m_value[2] + && m_value[3] == other.m_value[3]; + } + + bool operator!=(const zip_magic &other) const + { + return !(*this == other); + } + + char m_value[4]; +}; + +inline void istream_read_zip_magic(InputStream &istream, zip_magic &magic) +{ + istream.read(reinterpret_cast( magic.m_value ), 4); +} + +struct zip_version { + char version; + char ostype; +}; + +inline void istream_read_zip_version(InputStream &istream, zip_version &version) +{ + version.version = istream_read_byte(istream); + version.ostype = istream_read_byte(istream); +} + +struct zip_dostime { + unsigned short time; + unsigned short date; +}; + +inline void istream_read_zip_dostime(InputStream &istream, zip_dostime &dostime) +{ + dostime.time = istream_read_int16_le(istream); + dostime.date = istream_read_int16_le(istream); +} + +const zip_magic zip_file_header_magic = {{'P', 'K', 0x03, 0x04}}; + +/* A. Local file header */ +struct zip_file_header { + zip_magic z_magic; /* local file header signature (0x04034b50) */ + zip_version z_extract; /* version needed to extract */ + unsigned short z_flags; /* general purpose bit flag */ + unsigned short z_compr; /* compression method */ + zip_dostime z_dostime; /* last mod file time (dos format) */ + unsigned int z_crc32; /* crc-32 */ + unsigned int z_csize; /* compressed size */ + unsigned int z_usize; /* uncompressed size */ + unsigned short z_namlen; /* filename length (null if stdin) */ + unsigned short z_extras; /* extra field length */ + /* followed by filename (of variable size) */ + /* followed by extra field (of variable size) */ +}; + +inline void istream_read_zip_file_header(SeekableInputStream &istream, zip_file_header &file_header) +{ + istream_read_zip_magic(istream, file_header.z_magic); + istream_read_zip_version(istream, file_header.z_extract); + file_header.z_flags = istream_read_uint16_le(istream); + file_header.z_compr = istream_read_uint16_le(istream); + istream_read_zip_dostime(istream, file_header.z_dostime); + file_header.z_crc32 = istream_read_uint32_le(istream); + file_header.z_csize = istream_read_uint32_le(istream); + file_header.z_usize = istream_read_uint32_le(istream); + file_header.z_namlen = istream_read_uint16_le(istream); + file_header.z_extras = istream_read_uint16_le(istream); + istream.seek(file_header.z_namlen + file_header.z_extras, SeekableInputStream::cur); +} + +/* B. data descriptor + * the data descriptor exists only if bit 3 of z_flags is set. It is byte aligned + * and immediately follows the last byte of compressed data. It is only used if + * the output media of the compressor was not seekable, eg. standard output. + */ +const zip_magic zip_file_trailer_magic = {{'P', 'K', 0x07, 0x08}}; + +struct zip_file_trailer { + zip_magic z_magic; + unsigned int z_crc32; /* crc-32 */ + unsigned int z_csize; /* compressed size */ + unsigned int z_usize; /* uncompressed size */ +}; + +inline void istream_read_zip_file_trailer(InputStream &istream, zip_file_trailer &file_trailer) +{ + istream_read_zip_magic(istream, file_trailer.z_magic); + file_trailer.z_crc32 = istream_read_uint32_le(istream); + file_trailer.z_csize = istream_read_uint32_le(istream); + file_trailer.z_usize = istream_read_uint32_le(istream); +} + + +/* C. central directory structure: + [file header] . . . end of central dir record + */ + +/* directory file header + * - a single entry including filename, extras and comment may not exceed 64k. + */ + +const zip_magic zip_root_dirent_magic = {{'P', 'K', 0x01, 0x02}}; + +struct zip_root_dirent { + zip_magic z_magic; + zip_version z_encoder; /* version made by */ + zip_version z_extract; /* version need to extract */ + unsigned short z_flags; /* general purpose bit flag */ + unsigned short z_compr; /* compression method */ + zip_dostime z_dostime; /* last mod file time&date (dos format) */ + unsigned int z_crc32; /* crc-32 */ + unsigned int z_csize; /* compressed size */ + unsigned int z_usize; /* uncompressed size */ + unsigned short z_namlen; /* filename length (null if stdin) */ + unsigned short z_extras; /* extra field length */ + unsigned short z_comment; /* file comment length */ + unsigned short z_diskstart; /* disk number of start (if spanning zip over multiple disks) */ + unsigned short z_filetype; /* internal file attributes, bit0 = ascii */ + unsigned int z_filemode; /* extrnal file attributes, eg. msdos attrib byte */ + unsigned int z_off; /* relative offset of local file header, seekval if singledisk */ + /* followed by filename (of variable size) */ + /* followed by extra field (of variable size) */ + /* followed by file comment (of variable size) */ +}; + +inline void istream_read_zip_root_dirent(SeekableInputStream &istream, zip_root_dirent &root_dirent) +{ + istream_read_zip_magic(istream, root_dirent.z_magic); + istream_read_zip_version(istream, root_dirent.z_encoder); + istream_read_zip_version(istream, root_dirent.z_extract); + root_dirent.z_flags = istream_read_uint16_le(istream); + root_dirent.z_compr = istream_read_uint16_le(istream); + istream_read_zip_dostime(istream, root_dirent.z_dostime); + root_dirent.z_crc32 = istream_read_uint32_le(istream); + root_dirent.z_csize = istream_read_uint32_le(istream); + root_dirent.z_usize = istream_read_uint32_le(istream); + root_dirent.z_namlen = istream_read_uint16_le(istream); + root_dirent.z_extras = istream_read_uint16_le(istream); + root_dirent.z_comment = istream_read_uint16_le(istream); + root_dirent.z_diskstart = istream_read_uint16_le(istream); + root_dirent.z_filetype = istream_read_uint16_le(istream); + root_dirent.z_filemode = istream_read_uint32_le(istream); + root_dirent.z_off = istream_read_uint32_le(istream); + istream.seek(root_dirent.z_namlen + root_dirent.z_extras + root_dirent.z_comment, SeekableInputStream::cur); +} + +/* end of central dir record */ +const zip_magic zip_disk_trailer_magic = {{'P', 'K', 0x05, 0x06}}; +const unsigned int disk_trailer_length = 22; +struct zip_disk_trailer { + zip_magic z_magic; + unsigned short z_disk; /* number of this disk */ + unsigned short z_finaldisk; /* number of the disk with the start of the central dir */ + unsigned short z_entries; /* total number of entries in the central dir on this disk */ + unsigned short z_finalentries; /* total number of entries in the central dir */ + unsigned int z_rootsize; /* size of the central directory */ + unsigned int z_rootseek; /* offset of start of central directory with respect to * + * the starting disk number */ + unsigned short z_comment; /* zipfile comment length */ + /* followed by zipfile comment (of variable size) */ +}; + +inline void istream_read_zip_disk_trailer(SeekableInputStream &istream, zip_disk_trailer &disk_trailer) +{ + istream_read_zip_magic(istream, disk_trailer.z_magic); + disk_trailer.z_disk = istream_read_uint16_le(istream); + disk_trailer.z_finaldisk = istream_read_uint16_le(istream); + disk_trailer.z_entries = istream_read_uint16_le(istream); + disk_trailer.z_finalentries = istream_read_uint16_le(istream); + disk_trailer.z_rootsize = istream_read_uint32_le(istream); + disk_trailer.z_rootseek = istream_read_uint32_le(istream); + disk_trailer.z_comment = istream_read_uint16_le(istream); + istream.seek(disk_trailer.z_comment, SeekableInputStream::cur); +} + +inline SeekableStream::position_type pkzip_find_disk_trailer(SeekableInputStream &istream) +{ + istream.seek(0, SeekableInputStream::end); + SeekableStream::position_type start_position = istream.tell(); + if (start_position < disk_trailer_length) { + return 0; + } + start_position -= disk_trailer_length; + + zip_magic magic; + istream.seek(start_position); + istream_read_zip_magic(istream, magic); + + if (magic == zip_disk_trailer_magic) { + return start_position; + } else { + const SeekableStream::position_type max_comment = 0x10000; + const SeekableStream::position_type bufshift = 6; + const SeekableStream::position_type bufsize = max_comment >> bufshift; + unsigned char buffer[bufsize]; + + SeekableStream::position_type search_end = (max_comment < start_position) ? start_position - max_comment : 0; + SeekableStream::position_type position = start_position; + while (position != search_end) { + StreamBase::size_type to_read = std::min(bufsize, position - search_end); + position -= to_read; + + istream.seek(position); + StreamBase::size_type size = istream.read(buffer, to_read); + + unsigned char *p = buffer + size; + while (p != buffer) { + --p; + magic.m_value[3] = magic.m_value[2]; + magic.m_value[2] = magic.m_value[1]; + magic.m_value[1] = magic.m_value[0]; + magic.m_value[0] = *p; + if (magic == zip_disk_trailer_magic) { + return position + (p - buffer); + } + } + } + return 0; + } +} + +#endif diff --git a/plugins/archivezip/plugin.cpp b/plugins/archivezip/plugin.cpp new file mode 100644 index 0000000..eda88c3 --- /dev/null +++ b/plugins/archivezip/plugin.cpp @@ -0,0 +1,106 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" + +#include "debugging/debugging.h" +#include "modulesystem/singletonmodule.h" + +#include "archive.h" + + +class ArchiveZipAPI { + _QERArchiveTable m_archivezip; +public: + typedef _QERArchiveTable Type; + + STRING_CONSTANT(Name, "pk3"); + + ArchiveZipAPI() + { + m_archivezip.m_pfnOpenArchive = &OpenArchive; + } + + _QERArchiveTable *getTable() + { + return &m_archivezip; + } +}; + +typedef SingletonModule ArchiveZipModule; + +ArchiveZipModule g_ArchiveZipModule; + + +class ArchivePK4API { + _QERArchiveTable m_archivepk4; +public: + typedef _QERArchiveTable Type; + + STRING_CONSTANT(Name, "pk4"); + + ArchivePK4API() + { + m_archivepk4.m_pfnOpenArchive = &OpenArchive; + } + + _QERArchiveTable *getTable() + { + return &m_archivepk4; + } +}; + +typedef SingletonModule ArchivePK4Module; + +ArchivePK4Module g_ArchivePK4Module; + + +class ArchiveDPKAPI { + _QERArchiveTable m_archivedpk; +public: + typedef _QERArchiveTable Type; + + STRING_CONSTANT(Name, "dpk"); + + ArchiveDPKAPI() + { + m_archivedpk.m_pfnOpenArchive = &OpenArchive; + } + + _QERArchiveTable *getTable() + { + return &m_archivedpk; + } +}; + +typedef SingletonModule ArchiveDPKModule; + +ArchiveDPKModule g_ArchiveDPKModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer &server) +{ + initialiseModule(server); + + g_ArchiveZipModule.selfRegister(); + g_ArchivePK4Module.selfRegister(); + g_ArchiveDPKModule.selfRegister(); +} diff --git a/plugins/archivezip/zlibstream.h b/plugins/archivezip/zlibstream.h new file mode 100644 index 0000000..5e2b209 --- /dev/null +++ b/plugins/archivezip/zlibstream.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_ZLIBSTREAM_H ) +#define INCLUDED_ZLIBSTREAM_H + +#include "zlib.h" +#include "idatastream.h" + +/// \brief A wrapper around an InputStream of data compressed with the zlib deflate algorithm. +/// +/// - Uses z_stream to decompress the data stream on the fly. +/// - Uses a buffer to reduce the number of times the wrapped stream must be read. +class DeflatedInputStream : public InputStream { + InputStream &m_istream; + z_stream m_zipstream; + enum unnamed0 { m_bufsize = 1024 }; + unsigned char m_buffer[m_bufsize]; + +public: + DeflatedInputStream(InputStream &istream) + : m_istream(istream) + { + m_zipstream.zalloc = 0; + m_zipstream.zfree = 0; + m_zipstream.opaque = 0; + m_zipstream.avail_in = 0; + inflateInit2(&m_zipstream, -MAX_WBITS); + } + + ~DeflatedInputStream() + { + inflateEnd(&m_zipstream); + } + + size_type read(byte_type *buffer, size_type length) + { + m_zipstream.next_out = buffer; + m_zipstream.avail_out = static_cast( length ); + while (m_zipstream.avail_out != 0) { + if (m_zipstream.avail_in == 0) { + m_zipstream.next_in = m_buffer; + m_zipstream.avail_in = static_cast( m_istream.read(m_buffer, m_bufsize)); + } + if (inflate(&m_zipstream, Z_SYNC_FLUSH) != Z_OK) { + break; + } + } + return length - m_zipstream.avail_out; + } +}; + +#endif diff --git a/plugins/config.mk b/plugins/config.mk new file mode 100644 index 0000000..8b97a39 --- /dev/null +++ b/plugins/config.mk @@ -0,0 +1,32 @@ +# Common configuration options for all plugins + +CC=gcc +CXX=g++ +CFLAGS+=`gtk-config --cflags` -Wall -g -I../../include +CPPFLAGS+=`gtk-config --cflags` -Wall -g -I../../include +LDFLAGS+=`gtk-config --libs` -shared +OUTDIR=$(RADIANT_DATA)plugins +OBJS := $(patsubst %.cpp,%.o,$(filter %.cpp,$(SRC))) +OBJS += $(patsubst %.c,%.o,$(filter %.c,$(SRC))) + +all: $(OUTPUT) + +$(OUTPUT): $(OBJS) + $(CXX) -o $(OUTPUT) $(OBJS) $(LDFLAGS) + @if [ -d $(OUTDIR) ]; then cp $(OUTPUT) $(OUTDIR); fi + +## Other targets +.PHONY: clean + +clean: + rm -f *.o *.d $(OUTPUT) core + +## Dependencies +-include $(OBJS:.o=.d) + +%.d: %.cpp + @echo -n "$(@) " > $@ + @if { !(eval $(CXX) -MM $(CPPFLAGS) -w $<) >> $@; }; then \ + rm -f $@; exit 1; \ + fi + @[ -s $@ ] || rm -f $@ diff --git a/plugins/entity/CMakeLists.txt b/plugins/entity/CMakeLists.txt new file mode 100644 index 0000000..bd99916 --- /dev/null +++ b/plugins/entity/CMakeLists.txt @@ -0,0 +1,29 @@ +radiant_plugin(entity + angle.h + angles.h + colour.h + curve.h + eclassmodel.cpp eclassmodel.h + entity.cpp entity.h + filters.cpp filters.h + generic.cpp generic.h + group.cpp group.h + keyobservers.h + light.cpp light.h + miscmodel.cpp miscmodel.h + prop_dynamic.cpp prop_dynamic.h + model.h + modelskinkey.h + namedentity.h + namekeys.h + origin.h + plugin.cpp + rotation.h + scale.h + skincache.cpp skincache.h + targetable.cpp targetable.h + ) + +target_include_directories(entity + PRIVATE $ + ) diff --git a/plugins/entity/angle.h b/plugins/entity/angle.h new file mode 100644 index 0000000..775a712 --- /dev/null +++ b/plugins/entity/angle.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_ANGLE_H ) +#define INCLUDED_ANGLE_H + +#include "ientity.h" + +#include "math/quaternion.h" +#include "generic/callback.h" +#include "stringio.h" + +const float ANGLEKEY_IDENTITY = 0; + +inline void default_angle(float &angle) +{ + angle = ANGLEKEY_IDENTITY; +} + +inline void normalise_angle(float &angle) +{ + angle = static_cast( float_mod(angle, 360.0)); +} + +inline void read_angle(float &angle, const char *value) +{ + if (!string_parse_float(value, angle)) { + angle = 0; + } else { + normalise_angle(angle); + } +} + +inline void write_angle(float angle, Entity *entity) +{ + if (angle == 0) { + entity->setKeyValue("angle", ""); + } else { + char value[64]; + sprintf(value, "%f", angle); + entity->setKeyValue("angle", value); + } +} + +class AngleKey { + Callback m_angleChanged; +public: + float m_angle; + + + AngleKey(const Callback &angleChanged) + : m_angleChanged(angleChanged), m_angle(ANGLEKEY_IDENTITY) + { + } + + void angleChanged(const char *value) + { + read_angle(m_angle, value); + m_angleChanged(); + } + + typedef MemberCaller AngleChangedCaller; + + void write(Entity *entity) const + { + write_angle(m_angle, entity); + } +}; + +inline float angle_rotated(float angle, const Quaternion &rotation) +{ + return matrix4_get_rotation_euler_xyz_degrees( + matrix4_multiplied_by_matrix4( + matrix4_rotation_for_z_degrees(angle), + matrix4_rotation_for_quaternion_quantised(rotation) + ) + ).z(); +} + +#endif diff --git a/plugins/entity/angles.h b/plugins/entity/angles.h new file mode 100644 index 0000000..10e77bd --- /dev/null +++ b/plugins/entity/angles.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_ANGLES_H ) +#define INCLUDED_ANGLES_H + +#include "ientity.h" + +#include "math/quaternion.h" +#include "generic/callback.h" +#include "stringio.h" + +#include "angle.h" + +const Vector3 ANGLESKEY_IDENTITY = Vector3(0, 0, 0); + +inline void default_angles(Vector3 &angles) +{ + angles = ANGLESKEY_IDENTITY; +} + +inline void normalise_angles(Vector3 &angles) +{ + angles[0] = static_cast( float_mod(angles[0], 360)); + angles[1] = static_cast( float_mod(angles[1], 360)); + angles[2] = static_cast( float_mod(angles[2], 360)); +} + +inline void read_angle(Vector3 &angles, const char *value) +{ + if (!string_parse_float(value, angles[2])) { + default_angles(angles); + } else { + angles[0] = 0; + angles[1] = 0; + normalise_angles(angles); + } +} + +inline void read_angles(Vector3 &angles, const char *value) +{ + if (!string_parse_vector3(value, angles)) { + default_angles(angles); + } else { + angles = Vector3(angles[2], angles[0], angles[1]); + normalise_angles(angles); + } +} + +inline void write_angles(const Vector3 &angles, Entity *entity) +{ + if (angles[0] == 0 + && angles[1] == 0 + && angles[2] == 0) { + entity->setKeyValue("angle", ""); + entity->setKeyValue("angles", ""); + } else { + char value[64]; + + if (angles[0] == 0 && angles[1] == 0) { + entity->setKeyValue("angles", ""); + write_angle(angles[2], entity); + } else { + sprintf(value, "%f %f %f", angles[1], angles[2], angles[0]); + entity->setKeyValue("angle", ""); + entity->setKeyValue("angles", value); + } + } +} + +inline Vector3 angles_rotated(const Vector3 &angles, const Quaternion &rotation) +{ + return matrix4_get_rotation_euler_xyz_degrees( + matrix4_multiplied_by_matrix4( + matrix4_rotation_for_euler_xyz_degrees(angles), + matrix4_rotation_for_quaternion_quantised(rotation) + ) + ); +} + +class AnglesKey { + Callback m_anglesChanged; +public: + Vector3 m_angles; + + + AnglesKey(const Callback &anglesChanged) + : m_anglesChanged(anglesChanged), m_angles(ANGLESKEY_IDENTITY) + { + } + + void angleChanged(const char *value) + { + read_angle(m_angles, value); + m_anglesChanged(); + } + + typedef MemberCaller AngleChangedCaller; + + void anglesChanged(const char *value) + { + read_angles(m_angles, value); + m_anglesChanged(); + } + + typedef MemberCaller AnglesChangedCaller; + + void write(Entity *entity) const + { + write_angles(m_angles, entity); + } +}; + + +#endif diff --git a/plugins/entity/colour.h b/plugins/entity/colour.h new file mode 100644 index 0000000..441de95 --- /dev/null +++ b/plugins/entity/colour.h @@ -0,0 +1,126 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_COLOUR_H ) +#define INCLUDED_COLOUR_H + +#include "ientity.h" +#include "irender.h" + +#include "math/vector.h" +#include "eclasslib.h" +#include "generic/callback.h" +#include "stringio.h" + +inline void default_colour(Vector3 &colour) +{ + colour = Vector3(1, 1, 1); +} + +inline void read_colour(Vector3 &colour, const char *value) +{ + if (!string_parse_vector3(value, colour)) { + default_colour(colour); + } +} + +inline void read_colour255(Vector3 &colour, const char *value) +{ + if (!string_parse_vector3(value, colour)) { + default_colour(colour); + } else { + colour[0] /= 255; + colour[1] /= 255; + colour[2] /= 255; + } +} + +inline void write_colour(const Vector3 &colour, Entity *entity) +{ + char value[64]; + + sprintf(value, "%f %f %f", colour[0], colour[1], colour[2]); + entity->setKeyValue("_color", value); +} + +class Colour { + Callback m_colourChanged; + Shader *m_state; + + void capture_state() + { + m_state = colour_capture_state_fill(m_colour); + } + + void release_state() + { + colour_release_state_fill(m_colour); + } + +public: + Vector3 m_colour; + + Colour(const Callback &colourChanged) + : m_colourChanged(colourChanged) + { + default_colour(m_colour); + capture_state(); + } + + ~Colour() + { + release_state(); + } + + void colourChanged(const char *value) + { + release_state(); + read_colour(m_colour, value); + capture_state(); + + m_colourChanged(); + } + + void colour255Changed(const char *value) + { + release_state(); + read_colour255(m_colour, value); + capture_state(); + + m_colourChanged(); + } + + typedef MemberCaller ColourChangedCaller; + typedef MemberCaller Colour255ChangedCaller; + + + void write(Entity *entity) const + { + write_colour(m_colour, entity); + } + + Shader *state() const + { + return m_state; + } +}; + +#endif diff --git a/plugins/entity/curve.h b/plugins/entity/curve.h new file mode 100644 index 0000000..43dc9b3 --- /dev/null +++ b/plugins/entity/curve.h @@ -0,0 +1,452 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_CURVE_H ) +#define INCLUDED_CURVE_H + +#include "ientity.h" +#include "selectable.h" +#include "renderable.h" + +#include + +#include "math/curve.h" +#include "stream/stringstream.h" +#include "signal/signal.h" +#include "selectionlib.h" +#include "render.h" +#include "stringio.h" + +class RenderableCurve : public OpenGLRenderable { +public: + std::vector m_vertices; + + void render(RenderStateFlags state) const + { + pointvertex_gl_array(&m_vertices.front()); + glDrawArrays(GL_LINE_STRIP, 0, GLsizei(m_vertices.size())); + } +}; + +inline void plotBasisFunction(std::size_t numSegments, int point, int degree) +{ + Knots knots; + KnotVector_openUniform(knots, 4, degree); + + globalOutputStream() << "plotBasisFunction point " << point << " of 4, knot vector:"; + for (Knots::iterator i = knots.begin(); i != knots.end(); ++i) { + globalOutputStream() << " " << *i; + } + globalOutputStream() << "\n"; + globalOutputStream() << "t=0 basis=" << BSpline_basis(knots, point, degree, 0.0) << "\n"; + for (std::size_t i = 1; i < numSegments; ++i) { + double t = (1.0 / double(numSegments)) * double(i); + globalOutputStream() << "t=" << t << " basis=" << BSpline_basis(knots, point, degree, t) << "\n"; + } + globalOutputStream() << "t=1 basis=" << BSpline_basis(knots, point, degree, 1.0) << "\n"; +} + +inline bool ControlPoints_parse(ControlPoints &controlPoints, const char *value) +{ + StringTokeniser tokeniser(value, " "); + + std::size_t size; + if (!string_parse_size(tokeniser.getToken(), size)) { + return false; + } + + if (size < 3) { + return false; + } + controlPoints.resize(size); + + if (!string_equal(tokeniser.getToken(), "(")) { + return false; + } + for (ControlPoints::iterator i = controlPoints.begin(); i != controlPoints.end(); ++i) { + if (!string_parse_float(tokeniser.getToken(), (*i).x()) + || !string_parse_float(tokeniser.getToken(), (*i).y()) + || !string_parse_float(tokeniser.getToken(), (*i).z())) { + return false; + } + } + if (!string_equal(tokeniser.getToken(), ")")) { + return false; + } + return true; +} + +inline void ControlPoints_write(const ControlPoints &controlPoints, StringOutputStream &value) +{ + value << Unsigned(controlPoints.size()) << " ("; + for (ControlPoints::const_iterator i = controlPoints.begin(); i != controlPoints.end(); ++i) { + value << " " << (*i).x() << " " << (*i).y() << " " << (*i).z() << " "; + } + value << ")"; +} + +inline void +ControlPoint_testSelect(const Vector3 &point, ObservedSelectable &selectable, Selector &selector, SelectionTest &test) +{ + SelectionIntersection best; + test.TestPoint(point, best); + if (best.valid()) { + Selector_add(selector, selectable, best); + } +} + +class CurveEditType { +public: + Shader *m_controlsShader; + Shader *m_selectedShader; +}; + +inline void ControlPoints_write(ControlPoints &controlPoints, const char *key, Entity &entity) +{ + StringOutputStream value(256); + if (!controlPoints.empty()) { + ControlPoints_write(controlPoints, value); + } + entity.setKeyValue(key, value.c_str()); +} + +class CurveEdit { + SelectionChangeCallback m_selectionChanged; + ControlPoints &m_controlPoints; + typedef Array Selectables; + Selectables m_selectables; + + RenderablePointVector m_controlsRender; + mutable RenderablePointVector m_selectedRender; + +public: + typedef Static Type; + + CurveEdit(ControlPoints &controlPoints, const SelectionChangeCallback &selectionChanged) : + m_selectionChanged(selectionChanged), + m_controlPoints(controlPoints), + m_controlsRender(GL_POINTS), + m_selectedRender(GL_POINTS) + { + } + + template + const Functor &forEachSelected(const Functor &functor) + { + ASSERT_MESSAGE(m_controlPoints.size() == m_selectables.size(), "curve instance mismatch"); + ControlPoints::iterator p = m_controlPoints.begin(); + for (Selectables::iterator i = m_selectables.begin(); i != m_selectables.end(); ++i, ++p) { + if ((*i).isSelected()) { + functor(*p); + } + } + return functor; + } + + template + const Functor &forEachSelected(const Functor &functor) const + { + ASSERT_MESSAGE(m_controlPoints.size() == m_selectables.size(), "curve instance mismatch"); + ControlPoints::const_iterator p = m_controlPoints.begin(); + for (Selectables::const_iterator i = m_selectables.begin(); i != m_selectables.end(); ++i, ++p) { + if ((*i).isSelected()) { + functor(*p); + } + } + return functor; + } + + template + const Functor &forEach(const Functor &functor) const + { + for (ControlPoints::const_iterator i = m_controlPoints.begin(); i != m_controlPoints.end(); ++i) { + functor(*i); + } + return functor; + } + + void testSelect(Selector &selector, SelectionTest &test) + { + ASSERT_MESSAGE(m_controlPoints.size() == m_selectables.size(), "curve instance mismatch"); + ControlPoints::const_iterator p = m_controlPoints.begin(); + for (Selectables::iterator i = m_selectables.begin(); i != m_selectables.end(); ++i, ++p) { + ControlPoint_testSelect(*p, *i, selector, test); + } + } + + bool isSelected() const + { + for (Selectables::const_iterator i = m_selectables.begin(); i != m_selectables.end(); ++i) { + if ((*i).isSelected()) { + return true; + } + } + return false; + } + + void setSelected(bool selected) + { + for (Selectables::iterator i = m_selectables.begin(); i != m_selectables.end(); ++i) { + (*i).setSelected(selected); + } + } + + void write(const char *key, Entity &entity) + { + ControlPoints_write(m_controlPoints, key, entity); + } + + void transform(const Matrix4 &matrix) + { + forEachSelected([&](Vector3 &point) { + matrix4_transform_point(matrix, point); + }); + } + + void snapto(float snap) + { + forEachSelected([&](Vector3 &point) { + vector3_snap(point, snap); + }); + } + + void updateSelected() const + { + m_selectedRender.clear(); + forEachSelected([&](const Vector3 &point) { + m_selectedRender.push_back(PointVertex(vertex3f_for_vector3(point), colour_selected)); + }); + } + + void renderComponents(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld) const + { + renderer.SetState(Type::instance().m_controlsShader, Renderer::eWireframeOnly); + renderer.SetState(Type::instance().m_controlsShader, Renderer::eFullMaterials); + renderer.addRenderable(m_controlsRender, localToWorld); + } + + void renderComponentsSelected(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld) const + { + updateSelected(); + if (!m_selectedRender.empty()) { + renderer.Highlight(Renderer::ePrimitive, false); + renderer.SetState(Type::instance().m_selectedShader, Renderer::eWireframeOnly); + renderer.SetState(Type::instance().m_selectedShader, Renderer::eFullMaterials); + renderer.addRenderable(m_selectedRender, localToWorld); + } + } + + void curveChanged() + { + m_selectables.resize(m_controlPoints.size(), m_selectionChanged); + + m_controlsRender.clear(); + m_controlsRender.reserve(m_controlPoints.size()); + forEach([&](const Vector3 &point) { + m_controlsRender.push_back(PointVertex(vertex3f_for_vector3(point), colour_vertex)); + }); + + m_selectedRender.reserve(m_controlPoints.size()); + } + + typedef MemberCaller CurveChangedCaller; +}; + + +const int NURBS_degree = 3; + +class NURBSCurve { + Signal0 m_curveChanged; + Callback m_boundsChanged; +public: + ControlPoints m_controlPoints; + ControlPoints m_controlPointsTransformed; + NURBSWeights m_weights; + Knots m_knots; + RenderableCurve m_renderCurve; + AABB m_bounds; + + NURBSCurve(const Callback &boundsChanged) : m_boundsChanged(boundsChanged) + { + } + + SignalHandlerId connect(const SignalHandler &curveChanged) + { + curveChanged(); + return m_curveChanged.connectLast(curveChanged); + } + + void disconnect(SignalHandlerId id) + { + m_curveChanged.disconnect(id); + } + + void notify() + { + m_curveChanged(); + } + + void tesselate() + { + if (!m_controlPointsTransformed.empty()) { + const std::size_t numSegments = (m_controlPointsTransformed.size() - 1) * 16; + m_renderCurve.m_vertices.resize(numSegments + 1); + m_renderCurve.m_vertices[0].vertex = vertex3f_for_vector3(m_controlPointsTransformed[0]); + for (std::size_t i = 1; i < numSegments; ++i) { + m_renderCurve.m_vertices[i].vertex = vertex3f_for_vector3( + NURBS_evaluate(m_controlPointsTransformed, m_weights, m_knots, NURBS_degree, + (1.0 / double(numSegments)) * double(i))); + } + m_renderCurve.m_vertices[numSegments].vertex = vertex3f_for_vector3( + m_controlPointsTransformed[m_controlPointsTransformed.size() - 1]); + } else { + m_renderCurve.m_vertices.clear(); + } + } + + void curveChanged() + { + tesselate(); + + m_bounds = AABB(); + for (ControlPoints::iterator i = m_controlPointsTransformed.begin(); + i != m_controlPointsTransformed.end(); ++i) { + aabb_extend_by_point_safe(m_bounds, (*i)); + } + + m_boundsChanged(); + notify(); + } + + bool parseCurve(const char *value) + { + if (!ControlPoints_parse(m_controlPoints, value)) { + return false; + } + + m_weights.resize(m_controlPoints.size()); + for (NURBSWeights::iterator i = m_weights.begin(); i != m_weights.end(); ++i) { + (*i) = 1; + } + + KnotVector_openUniform(m_knots, m_controlPoints.size(), NURBS_degree); + + //plotBasisFunction(8, 0, NURBS_degree); + + return true; + } + + void curveChanged(const char *value) + { + if (string_empty(value) || !parseCurve(value)) { + m_controlPoints.resize(0); + m_knots.resize(0); + m_weights.resize(0); + } + m_controlPointsTransformed = m_controlPoints; + curveChanged(); + } + + typedef MemberCaller CurveChangedCaller; +}; + +class CatmullRomSpline { + Signal0 m_curveChanged; + Callback m_boundsChanged; +public: + ControlPoints m_controlPoints; + ControlPoints m_controlPointsTransformed; + RenderableCurve m_renderCurve; + AABB m_bounds; + + CatmullRomSpline(const Callback &boundsChanged) : m_boundsChanged(boundsChanged) + { + } + + SignalHandlerId connect(const SignalHandler &curveChanged) + { + curveChanged(); + return m_curveChanged.connectLast(curveChanged); + } + + void disconnect(SignalHandlerId id) + { + m_curveChanged.disconnect(id); + } + + void notify() + { + m_curveChanged(); + } + + void tesselate() + { + if (!m_controlPointsTransformed.empty()) { + const std::size_t numSegments = (m_controlPointsTransformed.size() - 1) * 16; + m_renderCurve.m_vertices.resize(numSegments + 1); + m_renderCurve.m_vertices[0].vertex = vertex3f_for_vector3(m_controlPointsTransformed[0]); + for (std::size_t i = 1; i < numSegments; ++i) { + m_renderCurve.m_vertices[i].vertex = vertex3f_for_vector3( + CatmullRom_evaluate(m_controlPointsTransformed, (1.0 / double(numSegments)) * double(i))); + } + m_renderCurve.m_vertices[numSegments].vertex = vertex3f_for_vector3( + m_controlPointsTransformed[m_controlPointsTransformed.size() - 1]); + } else { + m_renderCurve.m_vertices.clear(); + } + } + + bool parseCurve(const char *value) + { + return ControlPoints_parse(m_controlPoints, value); + } + + void curveChanged() + { + tesselate(); + + m_bounds = AABB(); + for (ControlPoints::iterator i = m_controlPointsTransformed.begin(); + i != m_controlPointsTransformed.end(); ++i) { + aabb_extend_by_point_safe(m_bounds, (*i)); + } + + m_boundsChanged(); + notify(); + } + + void curveChanged(const char *value) + { + if (string_empty(value) || !parseCurve(value)) { + m_controlPoints.resize(0); + } + m_controlPointsTransformed = m_controlPoints; + curveChanged(); + } + + typedef MemberCaller CurveChangedCaller; +}; + +const char *const curve_Nurbs = "curve_Nurbs"; +const char *const curve_CatmullRomSpline = "curve_CatmullRomSpline"; + + +#endif diff --git a/plugins/entity/doom3group.cpp b/plugins/entity/doom3group.cpp new file mode 100644 index 0000000..a891fd6 --- /dev/null +++ b/plugins/entity/doom3group.cpp @@ -0,0 +1,854 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 Represents any Doom3 entity which does not have a fixed size specified in its entity-definition (e.g. func_static). +/// +/// This entity behaves as a group only when the "model" key is empty or is the same as the "name" key. Otherwise it behaves as a model. +/// When behaving as a group, the "origin" key is the translation to be applied to all brushes (not patches) grouped under this entity. +/// When behaving as a model, the "origin", "angle" and "rotation" keys directly control the entity's local-to-parent transform. +/// When either the "curve_Nurbs" or "curve_CatmullRomSpline" keys define a curve, the curve is rendered and can be edited. + +#include "doom3group.h" + +#include "cullable.h" +#include "renderable.h" +#include "editable.h" +#include "modelskin.h" + +#include "selectionlib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "traverselib.h" +#include "entitylib.h" +#include "render.h" +#include "eclasslib.h" +#include "stream/stringstream.h" +#include "pivot.h" + +#include "targetable.h" +#include "origin.h" +#include "angle.h" +#include "rotation.h" +#include "model.h" +#include "filters.h" +#include "namedentity.h" +#include "keyobservers.h" +#include "namekeys.h" +#include "curve.h" +#include "modelskinkey.h" + +#include "entity.h" + +inline void +PointVertexArray_testSelect(PointVertex *first, std::size_t count, SelectionTest &test, SelectionIntersection &best) +{ + test.TestLineStrip( + VertexPointer( + reinterpret_cast( &first->vertex ), + sizeof(PointVertex) + ), + IndexPointer::index_type(count), + best + ); +} + +class Doom3Group : + public Bounded, + public Snappable { + EntityKeyValues m_entity; + KeyObserverMap m_keyObservers; + TraversableNodeSet m_traverse; + MatrixTransform m_transform; + + SingletonModel m_model; + OriginKey m_originKey; + Vector3 m_origin; + + RotationKey m_rotationKey; + Float9 m_rotation; + + ClassnameFilter m_filter; + NamedEntity m_named; + NameKeys m_nameKeys; + TraversableObserverPairRelay m_traverseObservers; + Doom3GroupOrigin m_funcStaticOrigin; + RenderablePivot m_renderOrigin; + RenderableNamedEntity m_renderName; + mutable Vector3 m_name_origin; + ModelSkinKey m_skin; + +public: + NURBSCurve m_curveNURBS; + SignalHandlerId m_curveNURBSChanged; + CatmullRomSpline m_curveCatmullRom; + SignalHandlerId m_curveCatmullRomChanged; +private: + mutable AABB m_curveBounds; + + Callback m_transformChanged; + Callback m_evaluateTransform; + + CopiedString m_name; + CopiedString m_modelKey; + bool m_isModel; + + scene::Traversable *m_traversable; + + void construct() + { + default_rotation(m_rotation); + + m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter)); + m_keyObservers.insert(Static::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named)); + m_keyObservers.insert("model", Doom3Group::ModelChangedCaller(*this)); + m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey)); + m_keyObservers.insert("angle", RotationKey::AngleChangedCaller(m_rotationKey)); + m_keyObservers.insert("rotation", RotationKey::RotationChangedCaller(m_rotationKey)); + m_keyObservers.insert("name", NameChangedCaller(*this)); + m_keyObservers.insert(curve_Nurbs, NURBSCurve::CurveChangedCaller(m_curveNURBS)); + m_keyObservers.insert(curve_CatmullRomSpline, CatmullRomSpline::CurveChangedCaller(m_curveCatmullRom)); + m_keyObservers.insert("skin", ModelSkinKey::SkinChangedCaller(m_skin)); + + m_traverseObservers.attach(m_funcStaticOrigin); + m_isModel = false; + m_nameKeys.setKeyIsName(keyIsNameDoom3Doom3Group); + attachTraverse(); + + m_entity.attach(m_keyObservers); + } + + void destroy() + { + m_entity.detach(m_keyObservers); + + if (isModel()) { + detachModel(); + } else { + detachTraverse(); + } + + m_traverseObservers.detach(m_funcStaticOrigin); + } + + void attachModel() + { + m_traversable = &m_model.getTraversable(); + m_model.attach(&m_traverseObservers); + } + + void detachModel() + { + m_traversable = 0; + m_model.detach(&m_traverseObservers); + } + + void attachTraverse() + { + m_traversable = &m_traverse; + m_traverse.attach(&m_traverseObservers); + } + + void detachTraverse() + { + m_traversable = 0; + m_traverse.detach(&m_traverseObservers); + } + + bool isModel() const + { + return m_isModel; + } + + void setIsModel(bool newValue) + { + if (newValue && !m_isModel) { + detachTraverse(); + attachModel(); + + m_nameKeys.setKeyIsName(Static::instance().m_keyIsName); + m_model.modelChanged(m_modelKey.c_str()); + } else if (!newValue && m_isModel) { + detachModel(); + attachTraverse(); + + m_nameKeys.setKeyIsName(keyIsNameDoom3Doom3Group); + } + m_isModel = newValue; + updateTransform(); + } + + void updateIsModel() + { + setIsModel(!string_equal(m_modelKey.c_str(), m_name.c_str())); + } + +// vc 2k5 compiler fix +#if _MSC_VER >= 1400 + public: +#endif + + void nameChanged(const char *value) + { + m_name = value; + updateIsModel(); + } + + typedef MemberCaller NameChangedCaller; + + void modelChanged(const char *value) + { + m_modelKey = value; + updateIsModel(); + if (isModel()) { + m_model.modelChanged(value); + } else { + m_model.modelChanged(""); + } + } + + typedef MemberCaller ModelChangedCaller; + + void updateTransform() + { + m_transform.localToParent() = g_matrix4_identity; + if (isModel()) { + matrix4_translate_by_vec3(m_transform.localToParent(), m_origin); + matrix4_multiply_by_matrix4(m_transform.localToParent(), rotation_toMatrix(m_rotation)); + } + m_transformChanged(); + if (!isModel()) { + m_funcStaticOrigin.originChanged(); + } + } + + typedef MemberCaller UpdateTransformCaller; + + void originChanged() + { + m_origin = m_originKey.m_origin; + updateTransform(); + } + + typedef MemberCaller OriginChangedCaller; + + void rotationChanged() + { + rotation_assign(m_rotation, m_rotationKey.m_rotation); + updateTransform(); + } + + typedef MemberCaller RotationChangedCaller; + + void skinChanged() + { + if (isModel()) { + scene::Node *node = m_model.getNode(); + if (node != 0) { + Node_modelSkinChanged(*node); + } + } + } + + typedef MemberCaller SkinChangedCaller; + +public: + Doom3Group(EntityClass *eclass, scene::Node &node, const Callback &transformChanged, + const Callback &boundsChanged, const Callback &evaluateTransform) : + m_entity(eclass), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_rotationKey(RotationChangedCaller(*this)), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_funcStaticOrigin(m_traverse, m_origin), + m_renderName(m_named, m_name_origin), + m_name_origin(g_vector3_identity), + m_skin(SkinChangedCaller(*this)), + m_curveNURBS(boundsChanged), + m_curveCatmullRom(boundsChanged), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform), + m_traversable(0) + { + construct(); + } + + Doom3Group(const Doom3Group &other, scene::Node &node, const Callback &transformChanged, + const Callback &boundsChanged, const Callback &evaluateTransform) : + m_entity(other.m_entity), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_rotationKey(RotationChangedCaller(*this)), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_funcStaticOrigin(m_traverse, m_origin), + m_renderName(m_named, g_vector3_identity), + m_skin(SkinChangedCaller(*this)), + m_curveNURBS(boundsChanged), + m_curveCatmullRom(boundsChanged), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform), + m_traversable(0) + { + construct(); + } + + ~Doom3Group() + { + destroy(); + } + + InstanceCounter m_instanceCounter; + + void instanceAttach(const scene::Path &path) + { + if (++m_instanceCounter.m_count == 1) { + m_filter.instanceAttach(); + m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_traverse.instanceAttach(path_find_mapfile(path.begin(), path.end())); + + m_funcStaticOrigin.enable(); + } + } + + void instanceDetach(const scene::Path &path) + { + if (--m_instanceCounter.m_count == 0) { + m_funcStaticOrigin.disable(); + + m_traverse.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_filter.instanceDetach(); + } + } + + EntityKeyValues &getEntity() + { + return m_entity; + } + + const EntityKeyValues &getEntity() const + { + return m_entity; + } + + scene::Traversable &getTraversable() + { + return *m_traversable; + } + + Namespaced &getNamespaced() + { + return m_nameKeys; + } + + Nameable &getNameable() + { + return m_named; + } + + TransformNode &getTransformNode() + { + return m_transform; + } + + ModelSkin &getModelSkin() + { + return m_skin.get(); + } + + void attach(scene::Traversable::Observer *observer) + { + m_traverseObservers.attach(*observer); + } + + void detach(scene::Traversable::Observer *observer) + { + m_traverseObservers.detach(*observer); + } + + const AABB &localAABB() const + { + m_curveBounds = m_curveNURBS.m_bounds; + aabb_extend_by_aabb_safe(m_curveBounds, m_curveCatmullRom.m_bounds); + return m_curveBounds; + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + if (isModel() && selected) { + m_renderOrigin.render(renderer, volume, localToWorld); + } + + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials); + + if (!m_curveNURBS.m_renderCurve.m_vertices.empty()) { + renderer.addRenderable(m_curveNURBS.m_renderCurve, localToWorld); + } + if (!m_curveCatmullRom.m_renderCurve.m_vertices.empty()) { + renderer.addRenderable(m_curveCatmullRom.m_renderCurve, localToWorld); + } + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected, + const AABB &childBounds) const + { + renderSolid(renderer, volume, localToWorld, selected); + + if (g_showNames) { + // draw models as usual + if (!isModel()) { + // don't draw the name for worldspawn + if (!strcmp(m_entity.getEntityClass().name(), "worldspawn")) { + return; + } + + // place name in the middle of the "children cloud" + m_name_origin = childBounds.origin; + } + + renderer.addRenderable(m_renderName, localToWorld); + } + } + + void testSelect(Selector &selector, SelectionTest &test, SelectionIntersection &best) + { + PointVertexArray_testSelect(&m_curveNURBS.m_renderCurve.m_vertices[0], + m_curveNURBS.m_renderCurve.m_vertices.size(), test, best); + PointVertexArray_testSelect(&m_curveCatmullRom.m_renderCurve.m_vertices[0], + m_curveCatmullRom.m_renderCurve.m_vertices.size(), test, best); + } + + void translate(const Vector3 &translation) + { + m_origin = origin_translated(m_origin, translation); + } + + void rotate(const Quaternion &rotation) + { + rotation_rotate(m_rotation, rotation); + } + + void snapto(float snap) + { + m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap); + m_originKey.write(&m_entity); + } + + void revertTransform() + { + m_origin = m_originKey.m_origin; + rotation_assign(m_rotation, m_rotationKey.m_rotation); + m_curveNURBS.m_controlPointsTransformed = m_curveNURBS.m_controlPoints; + m_curveCatmullRom.m_controlPointsTransformed = m_curveCatmullRom.m_controlPoints; + } + + void freezeTransform() + { + m_originKey.m_origin = m_origin; + m_originKey.write(&m_entity); + rotation_assign(m_rotationKey.m_rotation, m_rotation); + m_rotationKey.write(&m_entity); + m_curveNURBS.m_controlPoints = m_curveNURBS.m_controlPointsTransformed; + ControlPoints_write(m_curveNURBS.m_controlPoints, curve_Nurbs, m_entity); + m_curveCatmullRom.m_controlPoints = m_curveCatmullRom.m_controlPointsTransformed; + ControlPoints_write(m_curveCatmullRom.m_controlPoints, curve_CatmullRomSpline, m_entity); + } + + void transformChanged() + { + revertTransform(); + m_evaluateTransform(); + updateTransform(); + m_curveNURBS.curveChanged(); + m_curveCatmullRom.curveChanged(); + } + + typedef MemberCaller TransformChangedCaller; +}; + +class Doom3GroupInstance : + public TargetableInstance, + public TransformModifier, + public Renderable, + public SelectionTestable, + public ComponentSelectionTestable, + public ComponentEditable, + public ComponentSnappable { + class TypeCasts { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + m_casts = TargetableInstance::StaticTypeCasts::instance().get(); + 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); + } + + InstanceTypeCastTable &get() + { + return m_casts; + } + }; + + Doom3Group &m_contained; + CurveEdit m_curveNURBS; + CurveEdit m_curveCatmullRom; + mutable AABB m_aabb_component; +public: + + typedef LazyStatic StaticTypeCasts; + + + Bounded &get(NullType) + { + return m_contained; + } + + STRING_CONSTANT(Name, "Doom3GroupInstance"); + + Doom3GroupInstance(const scene::Path &path, scene::Instance *parent, Doom3Group &contained) : + TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this), + TransformModifier(Doom3Group::TransformChangedCaller(contained), ApplyTransformCaller(*this)), + m_contained(contained), + m_curveNURBS(m_contained.m_curveNURBS.m_controlPointsTransformed, SelectionChangedComponentCaller(*this)), + m_curveCatmullRom(m_contained.m_curveCatmullRom.m_controlPointsTransformed, + SelectionChangedComponentCaller(*this)) + { + m_contained.instanceAttach(Instance::path()); + m_contained.m_curveNURBSChanged = m_contained.m_curveNURBS.connect(CurveEdit::CurveChangedCaller(m_curveNURBS)); + m_contained.m_curveCatmullRomChanged = m_contained.m_curveCatmullRom.connect( + CurveEdit::CurveChangedCaller(m_curveCatmullRom)); + + StaticRenderableConnectionLines::instance().attach(*this); + } + + ~Doom3GroupInstance() + { + StaticRenderableConnectionLines::instance().detach(*this); + + m_contained.m_curveCatmullRom.disconnect(m_contained.m_curveCatmullRomChanged); + m_contained.m_curveNURBS.disconnect(m_contained.m_curveNURBSChanged); + m_contained.instanceDetach(Instance::path()); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderSolid(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + + m_curveNURBS.renderComponentsSelected(renderer, volume, localToWorld()); + m_curveCatmullRom.renderComponentsSelected(renderer, volume, localToWorld()); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), + Instance::childBounds()); + + m_curveNURBS.renderComponentsSelected(renderer, volume, localToWorld()); + m_curveCatmullRom.renderComponentsSelected(renderer, volume, localToWorld()); + } + + void renderComponents(Renderer &renderer, const VolumeTest &volume) const + { + if (GlobalSelectionSystem().ComponentMode() == SelectionSystem::eVertex) { + m_curveNURBS.renderComponents(renderer, volume, localToWorld()); + m_curveCatmullRom.renderComponents(renderer, volume, localToWorld()); + } + } + + void testSelect(Selector &selector, SelectionTest &test) + { + test.BeginMesh(localToWorld()); + SelectionIntersection best; + + m_contained.testSelect(selector, test, best); + + if (best.valid()) { + Selector_add(selector, getSelectable(), best); + } + } + + bool isSelectedComponents() const + { + return m_curveNURBS.isSelected() || m_curveCatmullRom.isSelected(); + } + + void setSelectedComponents(bool selected, SelectionSystem::EComponentMode mode) + { + if (mode == SelectionSystem::eVertex) { + m_curveNURBS.setSelected(selected); + m_curveCatmullRom.setSelected(selected); + } + } + + void testSelectComponents(Selector &selector, SelectionTest &test, SelectionSystem::EComponentMode mode) + { + if (mode == SelectionSystem::eVertex) { + test.BeginMesh(localToWorld()); + m_curveNURBS.testSelect(selector, test); + m_curveCatmullRom.testSelect(selector, test); + } + } + + void transformComponents(const Matrix4 &matrix) + { + if (m_curveNURBS.isSelected()) { + m_curveNURBS.transform(matrix); + } + if (m_curveCatmullRom.isSelected()) { + m_curveCatmullRom.transform(matrix); + } + } + + const AABB &getSelectedComponentsBounds() const + { + m_aabb_component = AABB(); + m_curveNURBS.forEachSelected([&](const Vector3 &point) { + aabb_extend_by_point_safe(m_aabb_component, point); + }); + m_curveCatmullRom.forEachSelected([&](const Vector3 &point) { + aabb_extend_by_point_safe(m_aabb_component, point); + }); + return m_aabb_component; + } + + void snapComponents(float snap) + { + if (m_curveNURBS.isSelected()) { + m_curveNURBS.snapto(snap); + m_curveNURBS.write(curve_Nurbs, m_contained.getEntity()); + } + if (m_curveCatmullRom.isSelected()) { + m_curveCatmullRom.snapto(snap); + m_curveCatmullRom.write(curve_CatmullRomSpline, m_contained.getEntity()); + } + } + + void evaluateTransform() + { + if (getType() == TRANSFORM_PRIMITIVE) { + m_contained.translate(getTranslation()); + m_contained.rotate(getRotation()); + } else { + transformComponents(calculateTransform()); + } + } + + void applyTransform() + { + m_contained.revertTransform(); + evaluateTransform(); + m_contained.freezeTransform(); + } + + typedef MemberCaller ApplyTransformCaller; + + void selectionChangedComponent(const Selectable &selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable); + GlobalSelectionSystem().onComponentSelection(*this, selectable); + } + + typedef MemberCaller SelectionChangedComponentCaller; +}; + +class Doom3GroupNode : + public scene::Node::Symbiot, + public scene::Instantiable, + public scene::Cloneable, + public scene::Traversable::Observer { + 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); + } + + NodeTypeCastTable &get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + Doom3Group m_contained; + + void construct() + { + m_contained.attach(this); + } + + void destroy() + { + m_contained.detach(this); + } + +public: + + typedef LazyStatic StaticTypeCasts; + + scene::Traversable &get(NullType) + { + return m_contained.getTraversable(); + } + + Snappable &get(NullType) + { + return m_contained; + } + + TransformNode &get(NullType) + { + return m_contained.getTransformNode(); + } + + Entity &get(NullType) + { + return m_contained.getEntity(); + } + + Nameable &get(NullType) + { + return m_contained.getNameable(); + } + + Namespaced &get(NullType) + { + return m_contained.getNamespaced(); + } + + ModelSkin &get(NullType) + { + return m_contained.getModelSkin(); + } + + Doom3GroupNode(EntityClass *eclass) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSet::BoundsChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + Doom3GroupNode(const Doom3GroupNode &other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + scene::Traversable::Observer(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSet::BoundsChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + ~Doom3GroupNode() + { + destroy(); + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + scene::Node &clone() const + { + return (new Doom3GroupNode(*this))->node(); + } + + void insert(scene::Node &child) + { + m_instances.insert(child); + } + + void erase(scene::Node &child) + { + m_instances.erase(child); + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new Doom3GroupInstance(path, parent, m_contained); + } + + 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); + } +}; + +void Doom3Group_construct() +{ + CurveEdit::Type::instance().m_controlsShader = GlobalShaderCache().capture("$POINT"); + CurveEdit::Type::instance().m_selectedShader = GlobalShaderCache().capture("$SELPOINT"); +} + +void Doom3Group_destroy() +{ + GlobalShaderCache().release("$SELPOINT"); + GlobalShaderCache().release("$POINT"); +} + +scene::Node &New_Doom3Group(EntityClass *eclass) +{ + return (new Doom3GroupNode(eclass))->node(); +} diff --git a/plugins/entity/doom3group.h b/plugins/entity/doom3group.h new file mode 100644 index 0000000..7eef334 --- /dev/null +++ b/plugins/entity/doom3group.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_STATIC_H ) +#define INCLUDED_STATIC_H + +namespace scene { + class Node; +} +class EntityClass; + +void Doom3Group_construct(); + +void Doom3Group_destroy(); + +scene::Node &New_Doom3Group(EntityClass *eclass); + +#endif diff --git a/plugins/entity/eclassmodel.cpp b/plugins/entity/eclassmodel.cpp new file mode 100644 index 0000000..56c51e9 --- /dev/null +++ b/plugins/entity/eclassmodel.cpp @@ -0,0 +1,542 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 Represents any entity which has a fixed size specified in its entity-definition and displays a model (e.g. ammo_bfg). +/// +/// This entity displays the model specified in its entity-definition. +/// The "origin" and "angle" keys directly control the entity's local-to-parent transform. +/// The "rotation" key directly controls the entity's local-to-parent transform for Doom3 only. + +#include "eclassmodel.h" + +#include "cullable.h" +#include "renderable.h" +#include "editable.h" + +#include "selectionlib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "traverselib.h" +#include "entitylib.h" +#include "render.h" +#include "eclasslib.h" +#include "pivot.h" + +#include "targetable.h" +#include "origin.h" +#include "angles.h" +#include "rotation.h" +#include "model.h" +#include "filters.h" +#include "namedentity.h" +#include "keyobservers.h" +#include "namekeys.h" +#include "modelskinkey.h" + +#include "entity.h" + +class EclassModel : + public Snappable { + MatrixTransform m_transform; + EntityKeyValues m_entity; + KeyObserverMap m_keyObservers; + + OriginKey m_originKey; + Vector3 m_origin; + AnglesKey m_anglesKey; + Vector3 m_angles; + RotationKey m_rotationKey; + Float9 m_rotation; + SingletonModel m_model; + + ClassnameFilter m_filter; + NamedEntity m_named; + NameKeys m_nameKeys; + RenderablePivot m_renderOrigin; + RenderableNamedEntity m_renderName; + ModelSkinKey m_skin; + + Callback m_transformChanged; + Callback m_evaluateTransform; + + void construct() + { + default_rotation(m_rotation); + m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter)); + m_keyObservers.insert(Static::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named)); + m_keyObservers.insert("angle", AnglesKey::AngleChangedCaller(m_anglesKey)); + m_keyObservers.insert("angles", AnglesKey::AnglesChangedCaller(m_anglesKey)); + m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey)); + } + +// vc 2k5 compiler fix +#if _MSC_VER >= 1400 + public: +#endif + + void updateTransform() + { + m_transform.localToParent() = g_matrix4_identity; + matrix4_translate_by_vec3(m_transform.localToParent(), m_origin); + matrix4_multiply_by_matrix4(m_transform.localToParent(), matrix4_rotation_for_euler_xyz_degrees(m_angles)); + m_transformChanged(); + } + + typedef MemberCaller UpdateTransformCaller; + + void originChanged() + { + m_origin = m_originKey.m_origin; + updateTransform(); + } + + typedef MemberCaller OriginChangedCaller; + + void anglesChanged() + { + m_angles = m_anglesKey.m_angles; + updateTransform(); + } + + typedef MemberCaller AnglesChangedCaller; + + void rotationChanged() + { + rotation_assign(m_rotation, m_rotationKey.m_rotation); + updateTransform(); + } + + typedef MemberCaller RotationChangedCaller; + + void skinChanged() + { + scene::Node *node = m_model.getNode(); + if (node != 0) { + Node_modelSkinChanged(*node); + } + } + + typedef MemberCaller SkinChangedCaller; + +public: + + EclassModel(EntityClass *eclass, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(eclass), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_rotationKey(RotationChangedCaller(*this)), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_renderName(m_named, g_vector3_identity), + m_skin(SkinChangedCaller(*this)), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + EclassModel(const EclassModel &other, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(other.m_entity), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_rotationKey(RotationChangedCaller(*this)), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_renderName(m_named, g_vector3_identity), + m_skin(SkinChangedCaller(*this)), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + InstanceCounter m_instanceCounter; + + void instanceAttach(const scene::Path &path) + { + if (++m_instanceCounter.m_count == 1) { + m_filter.instanceAttach(); + m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_entity.attach(m_keyObservers); + m_model.modelChanged(m_entity.getEntityClass().modelpath()); + m_skin.skinChanged(m_entity.getEntityClass().skin()); + } + } + + void instanceDetach(const scene::Path &path) + { + if (--m_instanceCounter.m_count == 0) { + m_skin.skinChanged(""); + m_model.modelChanged(""); + m_entity.detach(m_keyObservers); + m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_filter.instanceDetach(); + } + } + + EntityKeyValues &getEntity() + { + return m_entity; + } + + const EntityKeyValues &getEntity() const + { + return m_entity; + } + + scene::Traversable &getTraversable() + { + return m_model.getTraversable(); + } + + Namespaced &getNamespaced() + { + return m_nameKeys; + } + + Nameable &getNameable() + { + return m_named; + } + + TransformNode &getTransformNode() + { + return m_transform; + } + + ModelSkin &getModelSkin() + { + return m_skin.get(); + } + + void attach(scene::Traversable::Observer *observer) + { + m_model.attach(observer); + } + + void detach(scene::Traversable::Observer *observer) + { + m_model.detach(observer); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + if (selected) { + m_renderOrigin.render(renderer, volume, localToWorld); + } + + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + renderSolid(renderer, volume, localToWorld, selected); + if (g_showNames) { + renderer.addRenderable(m_renderName, localToWorld); + } + } + + void translate(const Vector3 &translation) + { + m_origin = origin_translated(m_origin, translation); + } + + void rotate(const Quaternion &rotation) + { + if (g_gameType == eGameTypeDoom3) { + rotation_rotate(m_rotation, rotation); + } else { + m_angles = angles_rotated(m_angles, rotation); + } + } + + void snapto(float snap) + { + m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap); + m_originKey.write(&m_entity); + } + + void revertTransform() + { + m_origin = m_originKey.m_origin; + if (g_gameType == eGameTypeDoom3) { + rotation_assign(m_rotation, m_rotationKey.m_rotation); + } else { + m_angles = m_anglesKey.m_angles; + } + } + + void freezeTransform() + { + m_originKey.m_origin = m_origin; + m_originKey.write(&m_entity); + if (g_gameType == eGameTypeDoom3) { + rotation_assign(m_rotationKey.m_rotation, m_rotation); + m_rotationKey.write(&m_entity); + } else { + m_anglesKey.m_angles = m_angles; + m_anglesKey.write(&m_entity); + } + } + + void transformChanged() + { + revertTransform(); + m_evaluateTransform(); + updateTransform(); + } + + typedef MemberCaller TransformChangedCaller; +}; + +class EclassModelInstance : public TargetableInstance, public TransformModifier, public Renderable { + class TypeCasts { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + m_casts = TargetableInstance::StaticTypeCasts::instance().get(); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceIdentityCast::install(m_casts); + } + + InstanceTypeCastTable &get() + { + return m_casts; + } + }; + + EclassModel &m_contained; +public: + typedef LazyStatic StaticTypeCasts; + + STRING_CONSTANT(Name, "EclassModelInstance"); + + EclassModelInstance(const scene::Path &path, scene::Instance *parent, EclassModel &contained) : + TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this), + TransformModifier(EclassModel::TransformChangedCaller(contained), ApplyTransformCaller(*this)), + m_contained(contained) + { + m_contained.instanceAttach(Instance::path()); + + StaticRenderableConnectionLines::instance().attach(*this); + } + + ~EclassModelInstance() + { + StaticRenderableConnectionLines::instance().detach(*this); + + m_contained.instanceDetach(Instance::path()); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderSolid(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void evaluateTransform() + { + if (getType() == TRANSFORM_PRIMITIVE) { + m_contained.translate(getTranslation()); + m_contained.rotate(getRotation()); + } + } + + void applyTransform() + { + m_contained.revertTransform(); + evaluateTransform(); + m_contained.freezeTransform(); + } + + typedef MemberCaller ApplyTransformCaller; +}; + +class EclassModelNode : + public scene::Node::Symbiot, + public scene::Instantiable, + public scene::Cloneable, + public scene::Traversable::Observer { + 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); + } + + NodeTypeCastTable &get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + EclassModel m_contained; + + void construct() + { + m_contained.attach(this); + } + + void destroy() + { + m_contained.detach(this); + } + +public: + typedef LazyStatic StaticTypeCasts; + + scene::Traversable &get(NullType) + { + return m_contained.getTraversable(); + } + + Snappable &get(NullType) + { + return m_contained; + } + + TransformNode &get(NullType) + { + return m_contained.getTransformNode(); + } + + Entity &get(NullType) + { + return m_contained.getEntity(); + } + + Nameable &get(NullType) + { + return m_contained.getNameable(); + } + + Namespaced &get(NullType) + { + return m_contained.getNamespaced(); + } + + ModelSkin &get(NullType) + { + return m_contained.getModelSkin(); + } + + EclassModelNode(EntityClass *eclass) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + EclassModelNode(const EclassModelNode &other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + scene::Traversable::Observer(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + ~EclassModelNode() + { + destroy(); + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + void insert(scene::Node &child) + { + m_instances.insert(child); + } + + void erase(scene::Node &child) + { + m_instances.erase(child); + } + + scene::Node &clone() const + { + return (new EclassModelNode(*this))->node(); + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new EclassModelInstance(path, parent, m_contained); + } + + 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 &New_EclassModel(EntityClass *eclass) +{ + return (new EclassModelNode(eclass))->node(); +} diff --git a/plugins/entity/eclassmodel.h b/plugins/entity/eclassmodel.h new file mode 100644 index 0000000..38bc0ed --- /dev/null +++ b/plugins/entity/eclassmodel.h @@ -0,0 +1,34 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_ECLASSMODEL_H ) +#define INCLUDED_ECLASSMODEL_H + +namespace scene { + class Node; +} +class EntityClass; + +scene::Node &New_EclassModel(EntityClass *eclass); + +#include "entity.h" + +#endif diff --git a/plugins/entity/entity.cpp b/plugins/entity/entity.cpp new file mode 100644 index 0000000..e95bdc8 --- /dev/null +++ b/plugins/entity/entity.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 "entity.h" + +#include "ifilter.h" +#include "selectable.h" +#include "namespace.h" + +#include "scenelib.h" +#include "entitylib.h" +#include "eclasslib.h" +#include "pivot.h" + +#include "targetable.h" +#include "uniquenames.h" +#include "namekeys.h" +#include "stream/stringstream.h" +#include "filters.h" + + +#include "miscmodel.h" +#include "prop_dynamic.h" +#include "light.h" +#include "group.h" +#include "eclassmodel.h" +#include "generic.h" +#include "doom3group.h" + + +EGameType g_gameType; + +inline scene::Node &entity_for_eclass(EntityClass *eclass) +{ + if ( classname_equal(eclass->name(), "prop_dynamic" ) ) { + return New_PropDynamic(eclass); + } if ( classname_equal(eclass->name(), "prop_static" ) ) { + return New_PropStatic(eclass); + } else if (classname_equal(eclass->name(), "light") + || classname_equal(eclass->name(), "lightJunior")) { + return New_Light(eclass); + } else if (classname_equal(eclass->name(), "env_sound") + || classname_equal(eclass->name(), "lightJunior")) { + return New_Light(eclass); + } + if (!eclass->fixedsize) { + /*if (g_gameType == eGameTypeDoom3) { + return New_Doom3Group(eclass); + } else {*/ + return New_Group(eclass); + /*}*/ + } else if (!string_empty(eclass->modelpath())) { + return New_EclassModel(eclass); + } else { + return New_GenericEntity(eclass); + } +} + +void Entity_setName(Entity &entity, const char *name) +{ + entity.setKeyValue("name", name); +} + +typedef ReferenceCaller EntitySetNameCaller; + +inline Namespaced *Node_getNamespaced(scene::Node &node) +{ + return NodeTypeCast::cast(node); +} + +inline scene::Node &node_for_eclass(EntityClass *eclass) +{ + scene::Node &node = entity_for_eclass(eclass); + Node_getEntity(node)->setKeyValue("classname", eclass->name()); + + /*if (string_not_empty(eclass->name()) + && !string_equal(eclass->name(), "worldspawn") + && !string_equal(eclass->name(), "UNKNOWN_CLASS")) { + char buffer[1024]; + strcpy(buffer, eclass->name()); + strcat(buffer, "_1"); + GlobalNamespace().makeUnique(buffer, EntitySetNameCaller(*Node_getEntity(node))); + }*/ + + Namespaced *namespaced = Node_getNamespaced(node); + if (namespaced != 0) { + namespaced->setNamespace(GlobalNamespace()); + } + + return node; +} + +EntityCreator::KeyValueChangedFunc EntityKeyValues::m_entityKeyValueChanged = 0; +EntityCreator::KeyValueChangedFunc KeyValue::m_entityKeyValueChanged = 0; +Counter *EntityKeyValues::m_counter = 0; + +bool g_showNames = false; +bool g_showAngles = false; +bool g_newLightDraw = true; +bool g_lightRadii = true; + +class ConnectEntities { +public: + Entity *m_e1; + Entity *m_e2; + int m_index; + + ConnectEntities(Entity *e1, Entity *e2, int index) : m_e1(e1), m_e2(e2), m_index(index) + { + } + + const char *keyname() + { + StringOutputStream key(16); + if (m_index <= 0) { + return "target"; + } + if (m_index == 1) { + return "killtarget"; + } + key << "target" << m_index; + return key.c_str(); + } + + void connect(const char *name) + { + m_e1->setKeyValue(keyname(), name); + m_e2->setKeyValue("targetname", name); + } + + typedef MemberCaller ConnectCaller; +}; + +inline Entity *ScenePath_getEntity(const scene::Path &path) +{ + Entity *entity = Node_getEntity(path.top()); + if (entity == 0) { + entity = Node_getEntity(path.parent()); + } + return entity; +} + +class Quake3EntityCreator : public EntityCreator { +public: + scene::Node &createEntity(EntityClass *eclass) + { + return node_for_eclass(eclass); + } + + void setKeyValueChangedFunc(KeyValueChangedFunc func) + { + EntityKeyValues::setKeyValueChangedFunc(func); + } + + void setCounter(Counter *counter) + { + EntityKeyValues::setCounter(counter); + } + + void connectEntities(const scene::Path &path, const scene::Path &targetPath, int index) + { + Entity *e1 = ScenePath_getEntity(path); + Entity *e2 = ScenePath_getEntity(targetPath); + + if (e1 == 0 || e2 == 0) { + globalErrorStream() << "entityConnectSelected: both of the selected instances must be an entity\n"; + return; + } + + if (e1 == e2) { + globalErrorStream() + << "entityConnectSelected: the selected instances must not both be from the same entity\n"; + return; + } + + + UndoableCommand undo("entityConnectSelected"); + + if (g_gameType == eGameTypeDoom3) { + StringOutputStream key(16); + if (index >= 0) { + key << "target"; + if (index != 0) { + key << index; + } + e1->setKeyValue(key.c_str(), e2->getKeyValue("name")); + key.clear(); + } else { + for (unsigned int i = 0;; ++i) { + key << "target"; + if (i != 0) { + key << i; + } + const char *value = e1->getKeyValue(key.c_str()); + if (string_empty(value)) { + e1->setKeyValue(key.c_str(), e2->getKeyValue("name")); + break; + } + key.clear(); + } + } + } else { + ConnectEntities connector(e1, e2, index); + const char *value = e2->getKeyValue("targetname"); + if (!string_empty(value)) { + connector.connect(value); + } else { + const char *type = e2->getKeyValue("classname"); + if (string_empty(type)) { + type = "t"; + } + StringOutputStream key(64); + key << type << "1"; + GlobalNamespace().makeUnique(key.c_str(), ConnectEntities::ConnectCaller(connector)); + } + } + + SceneChangeNotify(); + } + + void setLightRadii(bool lightRadii) + { + g_lightRadii = lightRadii; + } + + bool getLightRadii() const + { + return g_lightRadii; + } + + void setShowNames(bool showNames) + { + g_showNames = showNames; + } + + bool getShowNames() + { + return g_showNames; + } + + void setShowAngles(bool showAngles) + { + g_showAngles = showAngles; + } + + bool getShowAngles() + { + return g_showAngles; + } + + void printStatistics() const + { + StringPool_analyse(EntityKeyValues::getPool()); + } +}; + +Quake3EntityCreator g_Quake3EntityCreator; + +EntityCreator &GetEntityCreator() +{ + return g_Quake3EntityCreator; +} + + +class filter_entity_classname : public EntityFilter { + const char *m_classname; +public: + filter_entity_classname(const char *classname) : m_classname(classname) + { + } + + bool filter(const Entity &entity) const + { + return string_equal(entity.getKeyValue("classname"), m_classname); + } +}; + +class filter_entity_classgroup : public EntityFilter { + const char *m_classgroup; + std::size_t m_length; +public: + filter_entity_classgroup(const char *classgroup) : m_classgroup(classgroup), m_length(string_length(m_classgroup)) + { + } + + bool filter(const Entity &entity) const + { + return string_equal_n(entity.getKeyValue("classname"), m_classgroup, m_length); + } +}; + +/* this is such a bad hack because radiant sucks and I ran out of time */ +class filter_entity_classtwo: public EntityFilter { + const char *m_classname1; + const char *m_classname2; +public: + filter_entity_classtwo(const char *classname1, const char *classname2) : m_classname1(classname1), m_classname2(classname2) + { + } + + bool filter(const Entity &entity) const + { + return string_equal(entity.getKeyValue("classname"), m_classname1) || string_equal(entity.getKeyValue("classname"), m_classname2); + } +}; + +filter_entity_classtwo g_filter_entity_world("worldspawn", "func_group"); +filter_entity_classname g_filter_entity_func_group("func_group"); +filter_entity_classname g_filter_entity_light("light"); +filter_entity_classname g_filter_entity_prop_static("prop_static"); +filter_entity_classname g_filter_entity_prop_dynamic("prop_dynamic"); +filter_entity_classgroup g_filter_entity_trigger("trigger_"); +filter_entity_classgroup g_filter_entity_path("path_"); + +class filter_entity_doom3model : public EntityFilter { +public: + bool filter(const Entity &entity) const + { + return string_equal(entity.getKeyValue("classname"), "func_static") + && !string_equal(entity.getKeyValue("model"), entity.getKeyValue("name")); + } +}; + +filter_entity_doom3model g_filter_entity_doom3model; + + +void Entity_InitFilters() +{ + add_entity_filter(g_filter_entity_world, EXCLUDE_WORLD); + add_entity_filter(g_filter_entity_func_group, EXCLUDE_GROUP); + add_entity_filter(g_filter_entity_trigger, EXCLUDE_TRIGGERS); + add_entity_filter(g_filter_entity_prop_static, EXCLUDE_MODELS); + add_entity_filter(g_filter_entity_prop_dynamic, EXCLUDE_MODELS); + add_entity_filter(g_filter_entity_doom3model, EXCLUDE_MODELS); + add_entity_filter(g_filter_entity_light, EXCLUDE_LIGHTS); + add_entity_filter(g_filter_entity_path, EXCLUDE_PATHS); + add_entity_filter(g_filter_entity_world, EXCLUDE_ENT, true); +} + + +#include "preferencesystem.h" + +void Entity_Construct(EGameType gameType) +{ + g_gameType = gameType; + + Static::instance().m_keyIsName = keyIsNameQuake3; + Static::instance().m_nameKey = "targetname"; + + + GlobalPreferenceSystem().registerPreference("SI_ShowNames", make_property_string(g_showNames)); + GlobalPreferenceSystem().registerPreference("SI_ShowAngles", make_property_string(g_showAngles)); + GlobalPreferenceSystem().registerPreference("NewLightStyle", make_property_string(g_newLightDraw)); + GlobalPreferenceSystem().registerPreference("LightRadiuses", make_property_string(g_lightRadii)); + + Entity_InitFilters(); + Light_Construct(); + PropStatic_construct(); + PropDynamic_construct(); + /* Doom3Group_construct();*/ + + RenderablePivot::StaticShader::instance() = GlobalShaderCache().capture("$PIVOT"); + + GlobalShaderCache().attachRenderable(StaticRenderableConnectionLines::instance()); +} + +void Entity_Destroy() +{ + GlobalShaderCache().detachRenderable(StaticRenderableConnectionLines::instance()); + + GlobalShaderCache().release("$PIVOT"); + + /*Doom3Group_destroy();*/ + PropStatic_destroy(); + PropDynamic_destroy(); + Light_Destroy(); +} diff --git a/plugins/entity/entity.h b/plugins/entity/entity.h new file mode 100644 index 0000000..e6d8424 --- /dev/null +++ b/plugins/entity/entity.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_ENTITY_H ) +#define INCLUDED_ENTITY_H + +class EntityCreator; + +EntityCreator &GetEntityCreator(); + +enum EGameType { + eGameTypeQuake3, + eGameTypeRTCW, + eGameTypeDoom3, +}; + +extern EGameType g_gameType; + +class FilterSystem; + +void Entity_Construct(EGameType gameType = eGameTypeQuake3); + +void Entity_Destroy(); + +extern bool g_showNames; +extern bool g_showAngles; +extern bool g_newLightDraw; +extern bool g_lightRadii; + +#endif diff --git a/plugins/entity/entityq3.def b/plugins/entity/entityq3.def new file mode 100644 index 0000000..00d82f4 --- /dev/null +++ b/plugins/entity/entityq3.def @@ -0,0 +1,7 @@ +; entityq3.def : Declares the module parameters for the DLL. + +LIBRARY "ENTITYQ3" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/entity/filters.cpp b/plugins/entity/filters.cpp new file mode 100644 index 0000000..15c3787 --- /dev/null +++ b/plugins/entity/filters.cpp @@ -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 + */ + +#include "filters.h" + +#include "ifilter.h" + +#include + +class EntityFilterWrapper : public Filter { + bool m_active; + bool m_invert; + EntityFilter &m_filter; +public: + EntityFilterWrapper(EntityFilter &filter, bool invert) : m_invert(invert), m_filter(filter) + { + } + + void setActive(bool active) + { + m_active = active; + } + + bool active() + { + return m_active; + } + + bool filter(const Entity &entity) + { + return m_invert ^ m_filter.filter(entity); + } +}; + + +typedef std::list EntityFilters; +EntityFilters g_entityFilters; + +void add_entity_filter(EntityFilter &filter, int mask, bool invert) +{ + g_entityFilters.push_back(EntityFilterWrapper(filter, invert)); + GlobalFilterSystem().addFilter(g_entityFilters.back(), mask); +} + +bool entity_filtered(Entity &entity) +{ + for (EntityFilters::iterator i = g_entityFilters.begin(); i != g_entityFilters.end(); ++i) { + if ((*i).active() && (*i).filter(entity)) { + return true; + } + } + return false; +} diff --git a/plugins/entity/filters.h b/plugins/entity/filters.h new file mode 100644 index 0000000..e77f329 --- /dev/null +++ b/plugins/entity/filters.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +#include "ifilter.h" + +#include "generic/callback.h" +#include "scenelib.h" + +class Entity; + +class EntityFilter { +public: + virtual bool filter(const Entity &entity) const = 0; +}; + +bool entity_filtered(Entity &entity); + +void add_entity_filter(EntityFilter &filter, int mask, bool invert = false); + +class ClassnameFilter : public Filterable { + scene::Node &m_node; +public: + Entity &m_entity; + + ClassnameFilter(Entity &entity, scene::Node &node) : m_node(node), m_entity(entity) + { + } + + ~ClassnameFilter() + { + } + + void instanceAttach() + { + GlobalFilterSystem().registerFilterable(*this); + } + + void instanceDetach() + { + GlobalFilterSystem().unregisterFilterable(*this); + } + + void updateFiltered() + { + if (entity_filtered(m_entity)) { + m_node.enable(scene::Node::eFiltered); + } else { + m_node.disable(scene::Node::eFiltered); + } + } + + void classnameChanged(const char *value) + { + updateFiltered(); + } + + typedef MemberCaller ClassnameChangedCaller; +}; + +#endif diff --git a/plugins/entity/generic.cpp b/plugins/entity/generic.cpp new file mode 100644 index 0000000..cedbe1e --- /dev/null +++ b/plugins/entity/generic.cpp @@ -0,0 +1,532 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 Represents any entity which has a fixed size specified in its entity-definition and does not display a model (e.g. info_player_start). +/// +/// This entity displays an axis-aligned bounding box of the size and colour specified in its entity-definition. +/// The "origin" key directly controls the entity's local-to-parent transform. +/// An arrow is drawn to visualise the "angle" key. + +#include "cullable.h" +#include "renderable.h" +#include "editable.h" + +#include "math/frustum.h" +#include "selectionlib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "entitylib.h" +#include "render.h" +#include "eclasslib.h" +#include "math/line.h" + +#include "targetable.h" +#include "origin.h" +#include "angles.h" +#include "filters.h" +#include "namedentity.h" +#include "keyobservers.h" +#include "namekeys.h" +#include "rotation.h" + +#include "entity.h" + + +class RenderableArrow : public OpenGLRenderable { + const Vector3 &m_origin; + const Vector3 &m_angles; + +public: + RenderableArrow(const Vector3 &origin, const Vector3 &angles) + : m_origin(origin), m_angles(angles) + { + } + + void render(RenderStateFlags state) const + { + Matrix4 mat = matrix4_rotation_for_euler_xyz_degrees(m_angles); + arrow_draw(m_origin, matrix4_transformed_direction(mat, Vector3(1, 0, 0)), + matrix4_transformed_direction(mat, Vector3(0, 1, 0)), + matrix4_transformed_direction(mat, Vector3(0, 0, 1))); + } +}; + +inline void read_aabb(AABB &aabb, const EntityClass &eclass) +{ + aabb = aabb_for_minmax(eclass.mins, eclass.maxs); +} + + +class GenericEntity : + public Cullable, + public Bounded, + public Snappable { + EntityKeyValues m_entity; + KeyObserverMap m_keyObservers; + MatrixTransform m_transform; + + OriginKey m_originKey; + Vector3 m_origin; + AnglesKey m_anglesKey; + Vector3 m_angles; + + ClassnameFilter m_filter; + NamedEntity m_named; + NameKeys m_nameKeys; + + AABB m_aabb_local; + + RenderableArrow m_arrow; + RenderableSolidAABB m_aabb_solid; + RenderableWireframeAABB m_aabb_wire; + RenderableNamedEntity m_renderName; + + Callback m_transformChanged; + Callback m_evaluateTransform; + + void construct() + { + read_aabb(m_aabb_local, m_entity.getEntityClass()); + + m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter)); + m_keyObservers.insert(Static::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named)); + m_keyObservers.insert("angle", AnglesKey::AngleChangedCaller(m_anglesKey)); + m_keyObservers.insert("angles", AnglesKey::AnglesChangedCaller(m_anglesKey)); + m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey)); + } + +// vc 2k5 compiler fix +#if _MSC_VER >= 1400 + public: +#endif + + void updateTransform() + { + m_transform.localToParent() = g_matrix4_identity; + matrix4_translate_by_vec3(m_transform.localToParent(), m_origin); + m_transformChanged(); + } + + typedef MemberCaller UpdateTransformCaller; + + void originChanged() + { + m_origin = m_originKey.m_origin; + updateTransform(); + } + + typedef MemberCaller OriginChangedCaller; + + void anglesChanged() + { + m_angles = m_anglesKey.m_angles; + updateTransform(); + } + + typedef MemberCaller AnglesChangedCaller; +public: + + GenericEntity(EntityClass *eclass, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(eclass), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_arrow(m_aabb_local.origin, m_angles), + m_aabb_solid(m_aabb_local), + m_aabb_wire(m_aabb_local), + m_renderName(m_named, g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + GenericEntity(const GenericEntity &other, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(other.m_entity), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_arrow(m_aabb_local.origin, m_angles), + m_aabb_solid(m_aabb_local), + m_aabb_wire(m_aabb_local), + m_renderName(m_named, g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + InstanceCounter m_instanceCounter; + + void instanceAttach(const scene::Path &path) + { + if (++m_instanceCounter.m_count == 1) { + m_filter.instanceAttach(); + m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_entity.attach(m_keyObservers); + } + } + + void instanceDetach(const scene::Path &path) + { + if (--m_instanceCounter.m_count == 0) { + m_entity.detach(m_keyObservers); + m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_filter.instanceDetach(); + } + } + + EntityKeyValues &getEntity() + { + return m_entity; + } + + const EntityKeyValues &getEntity() const + { + return m_entity; + } + + Namespaced &getNamespaced() + { + return m_nameKeys; + } + + Nameable &getNameable() + { + return m_named; + } + + TransformNode &getTransformNode() + { + return m_transform; + } + + const AABB &localAABB() const + { + return m_aabb_local; + } + + VolumeIntersectionValue intersectVolume(const VolumeTest &volume, const Matrix4 &localToWorld) const + { + return volume.TestAABB(localAABB(), localToWorld); + } + + void renderArrow(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld) const + { + if (g_showAngles) { + renderer.addRenderable(m_arrow, localToWorld); + } + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld) const + { + renderer.SetState(m_entity.getEntityClass().m_state_fill, Renderer::eFullMaterials); + renderer.addRenderable(m_aabb_solid, localToWorld); + renderArrow(renderer, volume, localToWorld); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld) const + { + if (string_equal(m_entity.getKeyValue("classname"), "worldspawn")) { + /* todo: handle colors differently for worldspawn brushes */ + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + } else { + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + } + + renderer.addRenderable(m_aabb_wire, localToWorld); + renderArrow(renderer, volume, localToWorld); + if (g_showNames) { + renderer.addRenderable(m_renderName, 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); + } + } + + void translate(const Vector3 &translation) + { + m_origin = origin_translated(m_origin, translation); + } + + void rotate(const Quaternion &rotation) + { + m_angles = angles_rotated(m_angles, rotation); + } + + void snapto(float snap) + { + m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap); + m_originKey.write(&m_entity); + } + + void revertTransform() + { + m_origin = m_originKey.m_origin; + m_angles = m_anglesKey.m_angles; + } + + void freezeTransform() + { + m_originKey.m_origin = m_origin; + m_originKey.write(&m_entity); + m_anglesKey.m_angles = m_angles; + m_anglesKey.write(&m_entity); + } + + void transformChanged() + { + revertTransform(); + m_evaluateTransform(); + updateTransform(); + } + + typedef MemberCaller TransformChangedCaller; +}; + +class GenericEntityInstance : + public TargetableInstance, + public TransformModifier, + public Renderable, + public SelectionTestable { + class TypeCasts { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + m_casts = TargetableInstance::StaticTypeCasts::instance().get(); + InstanceContainedCast::install(m_casts); + InstanceContainedCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceIdentityCast::install(m_casts); + } + + InstanceTypeCastTable &get() + { + return m_casts; + } + }; + + GenericEntity &m_contained; + mutable AABB m_bounds; +public: + + typedef LazyStatic StaticTypeCasts; + + Bounded &get(NullType) + { + return m_contained; + } + + Cullable &get(NullType) + { + return m_contained; + } + + STRING_CONSTANT(Name, "GenericEntityInstance"); + + GenericEntityInstance(const scene::Path &path, scene::Instance *parent, GenericEntity &contained) : + TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this), + TransformModifier(GenericEntity::TransformChangedCaller(contained), ApplyTransformCaller(*this)), + m_contained(contained) + { + m_contained.instanceAttach(Instance::path()); + + StaticRenderableConnectionLines::instance().attach(*this); + } + + ~GenericEntityInstance() + { + StaticRenderableConnectionLines::instance().detach(*this); + + m_contained.instanceDetach(Instance::path()); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderSolid(renderer, volume, Instance::localToWorld()); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderWireframe(renderer, volume, Instance::localToWorld()); + } + + void testSelect(Selector &selector, SelectionTest &test) + { + m_contained.testSelect(selector, test, Instance::localToWorld()); + } + + void evaluateTransform() + { + if (getType() == TRANSFORM_PRIMITIVE) { + m_contained.translate(getTranslation()); + m_contained.rotate(getRotation()); + } + } + + void applyTransform() + { + m_contained.revertTransform(); + evaluateTransform(); + m_contained.freezeTransform(); + } + + typedef MemberCaller ApplyTransformCaller; +}; + +class GenericEntityNode : + 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); + } + + NodeTypeCastTable &get() + { + return m_casts; + } + }; + + + InstanceSet m_instances; + + scene::Node m_node; + GenericEntity m_contained; + +public: + typedef LazyStatic StaticTypeCasts; + + Snappable &get(NullType) + { + return m_contained; + } + + TransformNode &get(NullType) + { + return m_contained.getTransformNode(); + } + + Entity &get(NullType) + { + return m_contained.getEntity(); + } + + Nameable &get(NullType) + { + return m_contained.getNameable(); + } + + Namespaced &get(NullType) + { + return m_contained.getNamespaced(); + } + + GenericEntityNode(EntityClass *eclass) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + } + + GenericEntityNode(const GenericEntityNode &other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + scene::Node &clone() const + { + return (new GenericEntityNode(*this))->node(); + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new GenericEntityInstance(path, parent, m_contained); + } + + 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 &New_GenericEntity(EntityClass *eclass) +{ + return (new GenericEntityNode(eclass))->node(); +} diff --git a/plugins/entity/generic.h b/plugins/entity/generic.h new file mode 100644 index 0000000..1a04b95 --- /dev/null +++ b/plugins/entity/generic.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_GENERIC_H ) +#define INCLUDED_GENERIC_H + +scene::Node &New_GenericEntity(EntityClass *eclass); + +#endif diff --git a/plugins/entity/group.cpp b/plugins/entity/group.cpp new file mode 100644 index 0000000..ed8d577 --- /dev/null +++ b/plugins/entity/group.cpp @@ -0,0 +1,535 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 Represents any entity which does not have a fixed size specified in its entity-definition (except misc_model). +/// +/// This entity behaves as a group, i.e. it contains brushes. + +#include "cullable.h" +#include "renderable.h" +#include "editable.h" + +#include "selectionlib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "traverselib.h" +#include "entitylib.h" +#include "render.h" +#include "eclasslib.h" + +#include "targetable.h" +#include "origin.h" +#include "angles.h" +#include "scale.h" +#include "filters.h" +#include "namedentity.h" +#include "keyobservers.h" +#include "namekeys.h" + +#include "entity.h" + +/// The "origin" key directly controls the entity's local-to-parent transform. + +class Group { + EntityKeyValues m_entity; + KeyObserverMap m_keyObservers; + MatrixTransform m_transform; + TraversableNodeSet m_traverse; + + ClassnameFilter m_filter; + NamedEntity m_named; + NameKeys m_nameKeys; + + OriginKey m_originKey; + Vector3 m_origin; + + RenderableNamedEntity m_renderName; + mutable Vector3 m_name_origin; + + Callback m_transformChanged; + Callback m_evaluateTransform; + + void construct() + { + m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter)); + m_keyObservers.insert(Static::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named)); + m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey)); + } + +public: + Group(EntityClass *eclass, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(eclass), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_renderName(m_named, m_name_origin), + m_name_origin(g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + Group(const Group &other, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(other.m_entity), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_renderName(m_named, g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + InstanceCounter m_instanceCounter; + + void instanceAttach(const scene::Path &path) + { + if (++m_instanceCounter.m_count == 1) { + m_filter.instanceAttach(); + m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_traverse.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_entity.attach(m_keyObservers); + } + } + + void instanceDetach(const scene::Path &path) + { + if (--m_instanceCounter.m_count == 0) { + m_entity.detach(m_keyObservers); + m_traverse.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_filter.instanceDetach(); + } + } + + EntityKeyValues &getEntity() + { + return m_entity; + } + + const EntityKeyValues &getEntity() const + { + return m_entity; + } + + scene::Traversable &getTraversable() + { + return m_traverse; + } + + Namespaced &getNamespaced() + { + return m_nameKeys; + } + + Nameable &getNameable() + { + return m_named; + } + + TransformNode &getTransformNode() + { + return m_transform; + } + + void attach(scene::Traversable::Observer *observer) + { + m_traverse.attach(observer); + } + + void detach(scene::Traversable::Observer *observer) + { + m_traverse.detach(observer); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld) const + { + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, + const AABB &childBounds) const + { + renderSolid(renderer, volume, localToWorld); + + if (g_showNames) { + // don't draw the name for worldspawn + if (!strcmp(m_entity.getEntityClass().name(), "worldspawn")) { + return; + } + + // place name in the middle of the "children cloud" + m_name_origin = childBounds.origin; + + renderer.addRenderable(m_renderName, localToWorld); + } + } + + void updateTransform() + { + m_transform.localToParent() = g_matrix4_identity; + matrix4_translate_by_vec3(m_transform.localToParent(), m_origin); + m_transformChanged(); + } + + typedef MemberCaller UpdateTransformCaller; + + void originChanged() + { + m_origin = m_originKey.m_origin; + updateTransform(); + } + + typedef MemberCaller OriginChangedCaller; + + void translate(const Vector3 &translation) + { + m_origin = origin_translated(m_origin, translation); + } + + void revertTransform() + { + m_origin = m_originKey.m_origin; + } + + void freezeTransform() + { + m_originKey.m_origin = m_origin; + m_originKey.write(&m_entity); + } + + void transformChanged() + { + revertTransform(); + m_evaluateTransform(); + updateTransform(); + } + + typedef MemberCaller TransformChangedCaller; +}; + +#if 0 +class TransformableSetTranslation +{ +Translation m_value; +public: +TransformableSetTranslation( const Translation& value ) : m_value( value ){ +} +void operator()( Transformable& transformable ) const { + transformable.setTranslation( m_value ); +} +}; + +class TransformableSetRotation +{ +Rotation m_value; +public: +TransformableSetRotation( const Rotation& value ) : m_value( value ){ +} +void operator()( Transformable& transformable ) const { + transformable.setRotation( m_value ); +} +}; + +class TransformableSetScale +{ +Scale m_value; +public: +TransformableSetScale( const Scale& value ) : m_value( value ){ +} +void operator()( Transformable& transformable ) const { + transformable.setScale( m_value ); +} +}; + +class TransformableSetType +{ +TransformModifierType m_value; +public: +TransformableSetType( const TransformModifierType& value ) : m_value( value ){ +} +void operator()( Transformable& transformable ) const { + transformable.setType( m_value ); +} +}; + +class TransformableFreezeTransform +{ +TransformModifierType m_value; +public: +void operator()( Transformable& transformable ) const { + transformable.freezeTransform(); +} +}; + +template +inline void Scene_forEachChildTransformable( const Functor& functor, const scene::Path& path ){ + GlobalSceneGraph().traverse_subgraph( ChildInstanceWalker< InstanceApply >( functor ), path ); +} +#endif + +class GroupInstance : + public TargetableInstance, + public TransformModifier, +#if 0 + public Transformable, +#endif + public Renderable { + class TypeCasts { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + m_casts = TargetableInstance::StaticTypeCasts::instance().get(); + InstanceStaticCast::install(m_casts); +#if 0 + InstanceStaticCast::install( m_casts ); +#endif + } + + InstanceTypeCastTable &get() + { + return m_casts; + } + }; + + Group &m_contained; +public: + typedef LazyStatic StaticTypeCasts; + + GroupInstance(const scene::Path &path, scene::Instance *parent, Group &group) : + TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), group.getEntity(), *this), + TransformModifier(Group::TransformChangedCaller(group), ApplyTransformCaller(*this)), + m_contained(group) + { + m_contained.instanceAttach(Instance::path()); + StaticRenderableConnectionLines::instance().attach(*this); + } + + ~GroupInstance() + { + StaticRenderableConnectionLines::instance().detach(*this); + m_contained.instanceDetach(Instance::path()); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderSolid(renderer, volume, Instance::localToWorld()); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), Instance::childBounds()); + } + + STRING_CONSTANT(Name, "GroupInstance"); + +#if 0 + void setType( TransformModifierType type ){ + Scene_forEachChildTransformable( TransformableSetType( type ), Instance::path() ); + } + void setTranslation( const Translation& value ){ + Scene_forEachChildTransformable( TransformableSetTranslation( value ), Instance::path() ); + } + void setRotation( const Rotation& value ){ + Scene_forEachChildTransformable( TransformableSetRotation( value ), Instance::path() ); + } + void setScale( const Scale& value ){ + Scene_forEachChildTransformable( TransformableSetScale( value ), Instance::path() ); + } + void freezeTransform(){ + Scene_forEachChildTransformable( TransformableFreezeTransform(), Instance::path() ); + } + + void evaluateTransform(){ + } +#endif + + void evaluateTransform() + { + if (getType() == TRANSFORM_PRIMITIVE) { + m_contained.translate(getTranslation()); + } + } + + void applyTransform() + { + m_contained.revertTransform(); + evaluateTransform(); + m_contained.freezeTransform(); + } + + typedef MemberCaller ApplyTransformCaller; +}; + +class GroupNode : + public scene::Node::Symbiot, + public scene::Instantiable, + public scene::Cloneable, + public scene::Traversable::Observer { + 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); + } + + NodeTypeCastTable &get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + Group m_contained; + + void construct() + { + m_contained.attach(this); + } + + void destroy() + { + m_contained.detach(this); + } + +public: + + typedef LazyStatic StaticTypeCasts; + + scene::Traversable &get(NullType) + { + return m_contained.getTraversable(); + } + + TransformNode &get(NullType) + { + return m_contained.getTransformNode(); + } + + Entity &get(NullType) + { + return m_contained.getEntity(); + } + + Nameable &get(NullType) + { + return m_contained.getNameable(); + } + + Namespaced &get(NullType) + { + return m_contained.getNamespaced(); + } + + GroupNode(EntityClass *eclass) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + GroupNode(const GroupNode &other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + scene::Traversable::Observer(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + ~GroupNode() + { + destroy(); + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + scene::Node &clone() const + { + return (new GroupNode(*this))->node(); + } + + void insert(scene::Node &child) + { + m_instances.insert(child); + } + + void erase(scene::Node &child) + { + m_instances.erase(child); + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new GroupInstance(path, parent, m_contained); + } + + 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 &New_Group(EntityClass *eclass) +{ + return (new GroupNode(eclass))->node(); +} diff --git a/plugins/entity/group.h b/plugins/entity/group.h new file mode 100644 index 0000000..c8c8353 --- /dev/null +++ b/plugins/entity/group.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_GROUP_H ) +#define INCLUDED_GROUP_H + +scene::Node &New_Group(EntityClass *eclass); + +#endif diff --git a/plugins/entity/keyobservers.h b/plugins/entity/keyobservers.h new file mode 100644 index 0000000..7859735 --- /dev/null +++ b/plugins/entity/keyobservers.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_KEYOBSERVERS_H ) +#define INCLUDED_KEYOBSERVERS_H + +#include "entitylib.h" +#include + +class KeyObserverMap : public Entity::Observer { + typedef std::multimap KeyObservers; + KeyObservers m_keyObservers; +public: + void insert(const char *key, const KeyObserver &observer) + { + m_keyObservers.insert(KeyObservers::value_type(key, observer)); + } + + void insert(const char *key, EntityKeyValue &value) + { + for (KeyObservers::const_iterator i = m_keyObservers.find(key); + i != m_keyObservers.end() && string_equal((*i).first, key); ++i) { + value.attach((*i).second); + } + } + + void erase(const char *key, EntityKeyValue &value) + { + for (KeyObservers::const_iterator i = m_keyObservers.find(key); + i != m_keyObservers.end() && string_equal((*i).first, key); ++i) { + value.detach((*i).second); + } + } +}; + +#endif diff --git a/plugins/entity/light.cpp b/plugins/entity/light.cpp new file mode 100644 index 0000000..04596fd --- /dev/null +++ b/plugins/entity/light.cpp @@ -0,0 +1,1680 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 Represents any light entity (e.g. light). +/// +/// This entity dislays a special 'light' model. +/// The "origin" key directly controls the position of the light model in local space. +/// The "_color" key controls the colour of the light model. +/// The "light" key is visualised with a sphere representing the approximate coverage of the light (except Doom3). +/// Doom3 special behaviour: +/// The entity behaves as a group. +/// The "origin" key is the translation to be applied to all brushes (not patches) grouped under this entity. +/// The "light_center" and "light_radius" keys are visualised with a point and a box when the light is selected. +/// The "rotation" key directly controls the orientation of the light bounding box in local space. +/// The "light_origin" key controls the position of the light independently of the "origin" key if it is specified. +/// The "light_rotation" key duplicates the behaviour of the "rotation" key if it is specified. This appears to be an unfinished feature in Doom3. + +#include "light.h" + +#include + +#include "cullable.h" +#include "renderable.h" +#include "editable.h" + +#include "math/frustum.h" +#include "selectionlib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "entitylib.h" +#include "render.h" +#include "eclasslib.h" +#include "render.h" +#include "stringio.h" +#include "traverselib.h" +#include "dragplanes.h" + +#include "targetable.h" +#include "origin.h" +#include "colour.h" +#include "filters.h" +#include "namedentity.h" +#include "keyobservers.h" +#include "namekeys.h" +#include "rotation.h" + +#include "entity.h" + +extern bool g_newLightDraw; + + +void sphere_draw_fill(const Vector3 &origin, float radius, int sides) +{ + if (radius <= 0) { + return; + } + + const double dt = c_2pi / static_cast( sides ); + const double dp = c_pi / static_cast( sides ); + + glBegin(GL_TRIANGLES); + for (int i = 0; i <= sides - 1; ++i) { + for (int j = 0; j <= sides - 2; ++j) { + const double t = i * dt; + const double p = (j * dp) - (c_pi / 2.0); + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + } + } + + { + const double p = (sides - 1) * dp - (c_pi / 2.0); + for (int i = 0; i <= sides - 1; ++i) { + const double t = i * dt; + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + } + } + glEnd(); +} + +void sphere_draw_wire(const Vector3 &origin, float radius, int sides) +{ + { + glBegin(GL_LINE_LOOP); + + for (int i = 0; i <= sides; i++) { + double ds = sin((i * 2 * c_pi) / sides); + double dc = cos((i * 2 * c_pi) / sides); + + glVertex3f( + static_cast( origin[0] + radius * dc ), + static_cast( origin[1] + radius * ds ), + origin[2] + ); + } + + glEnd(); + } + + { + glBegin(GL_LINE_LOOP); + + for (int i = 0; i <= sides; i++) { + double ds = sin((i * 2 * c_pi) / sides); + double dc = cos((i * 2 * c_pi) / sides); + + glVertex3f( + static_cast( origin[0] + radius * dc ), + origin[1], + static_cast( origin[2] + radius * ds ) + ); + } + + glEnd(); + } + + { + glBegin(GL_LINE_LOOP); + + for (int i = 0; i <= sides; i++) { + double ds = sin((i * 2 * c_pi) / sides); + double dc = cos((i * 2 * c_pi) / sides); + + glVertex3f( + origin[0], + static_cast( origin[1] + radius * dc ), + static_cast( origin[2] + radius * ds ) + ); + } + + glEnd(); + } +} + +void light_draw_box_lines(const Vector3 &origin, const Vector3 points[8]) +{ + //draw lines from the center of the bbox to the corners + glBegin(GL_LINES); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[1])); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[5])); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[2])); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[6])); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[0])); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[4])); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[3])); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(points[7])); + + glEnd(); +} + +void light_draw_radius_wire(const Vector3 &origin, const float envelope[3]) +{ + if (envelope[0] > 0) { + sphere_draw_wire(origin, envelope[0], 24); + } + if (envelope[1] > 0) { + sphere_draw_wire(origin, envelope[1], 24); + } + if (envelope[2] > 0) { + sphere_draw_wire(origin, envelope[2], 24); + } +} + +void light_draw_radius_fill(const Vector3 &origin, const float envelope[3]) +{ + if (envelope[0] > 0) { + sphere_draw_fill(origin, envelope[0], 16); + } + if (envelope[1] > 0) { + sphere_draw_fill(origin, envelope[1], 16); + } + if (envelope[2] > 0) { + sphere_draw_fill(origin, envelope[2], 16); + } +} + +void light_vertices(const AABB &aabb_light, Vector3 points[6]) +{ + Vector3 max(vector3_added(aabb_light.origin, aabb_light.extents)); + Vector3 min(vector3_subtracted(aabb_light.origin, aabb_light.extents)); + Vector3 mid(aabb_light.origin); + + // top, bottom, middle-up, middle-right, middle-down, middle-left + points[0] = Vector3(mid[0], mid[1], max[2]); + points[1] = Vector3(mid[0], mid[1], min[2]); + points[2] = Vector3(mid[0], max[1], mid[2]); + points[3] = Vector3(max[0], mid[1], mid[2]); + points[4] = Vector3(mid[0], min[1], mid[2]); + points[5] = Vector3(min[0], mid[1], mid[2]); +} + +void light_draw(const AABB &aabb_light, RenderStateFlags state) +{ + Vector3 points[6]; + light_vertices(aabb_light, points); + + typedef unsigned int index_t; + const index_t indices[24] = { + 0, 2, 3, + 0, 3, 4, + 0, 4, 5, + 0, 5, 2, + 1, 2, 5, + 1, 5, 4, + 1, 4, 3, + 1, 3, 2 + }; + + glVertexPointer(3, GL_FLOAT, 0, points); + glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(index_t), RenderIndexTypeID, indices); +} + +// These variables are tweakable on the q3map2 console, setting to q3map2 +// default here as there is no way to find out what the user actually uses +// right now. Maybe move them to worldspawn? +float fPointScale = 7500.f; +float fLinearScale = 1.f / 8000.f; + +float light_radius_linear(float fIntensity, float fFalloffTolerance) +{ + return ((fIntensity * fPointScale * fLinearScale) - fFalloffTolerance); +} + +float light_radius(float fIntensity, float fFalloffTolerance) +{ + return sqrt(fIntensity * fPointScale / fFalloffTolerance); +} + +bool spawnflags_linear(int flags) +{ + return (flags & 1); +} + +class LightRadii { +public: + float m_radii[3]; + +private: + float m_primaryIntensity; + float m_secondaryIntensity; + int m_flags; + float m_fade; + float m_scale; + + void calculateRadii() + { + float intensity = 300.0f; + + if (m_primaryIntensity != 0.0f) { + intensity = m_primaryIntensity; + } else if (m_secondaryIntensity != 0.0f) { + intensity = m_secondaryIntensity; + } + + if (m_scale) { + intensity = m_scale * 0.5f; + m_radii[0] = light_radius(intensity, 1.0f); + m_radii[1] = light_radius(intensity, 48.0f); + m_radii[2] = light_radius(intensity, 255.0f); + } else { + if (spawnflags_linear(m_flags)) { + m_radii[0] = light_radius_linear(intensity, 1.0f) / m_fade; + m_radii[1] = light_radius_linear(intensity, 48.0f) / m_fade; + m_radii[2] = light_radius_linear(intensity, 255.0f) / m_fade; + } else { + m_radii[0] = light_radius(intensity, 1.0f); + m_radii[1] = light_radius(intensity, 48.0f); + m_radii[2] = light_radius(intensity, 255.0f); + } + } + } + +public: + LightRadii() : m_primaryIntensity(0), m_secondaryIntensity(0), m_flags(0), m_fade(1), m_scale(1) + { + } + + + void primaryIntensityChanged(const char *value) + { + m_primaryIntensity = string_read_float(value); + calculateRadii(); + } + + typedef MemberCaller PrimaryIntensityChangedCaller; + + void secondaryIntensityChanged(const char *value) + { + m_secondaryIntensity = string_read_float(value); + calculateRadii(); + } + + typedef MemberCaller SecondaryIntensityChangedCaller; + + void scaleChanged(const char *value) + { + m_scale = string_read_float(value); + calculateRadii(); + } + + typedef MemberCaller ScaleChangedCaller; + + void fadeChanged(const char *value) + { + m_fade = string_read_float(value); + if (m_fade <= 0.0f) { + m_fade = 1.0f; + } + calculateRadii(); + } + + typedef MemberCaller FadeChangedCaller; + + void flagsChanged(const char *value) + { + m_flags = string_read_int(value); + calculateRadii(); + } + + typedef MemberCaller FlagsChangedCaller; +}; + +class LightRadius { +public: + Vector3 m_defaultRadius; + Vector3 m_radius; + Vector3 m_radiusTransformed; + Vector3 m_center; + Callback m_changed; + bool m_useCenterKey; + + LightRadius(const char *defaultRadius) : m_defaultRadius(300, 300, 300), m_center(0, 0, 0), + m_useCenterKey(false) + { + if (!string_parse_vector3(defaultRadius, m_defaultRadius)) { + globalErrorStream() << "LightRadius: failed to parse default light radius\n"; + } + m_radius = m_defaultRadius; + } + + void lightRadiusChanged(const char *value) + { + if (!string_parse_vector3(value, m_radius)) { + m_radius = m_defaultRadius; + } + m_radiusTransformed = m_radius; + m_changed(); + SceneChangeNotify(); + } + + typedef MemberCaller LightRadiusChangedCaller; + + void lightCenterChanged(const char *value) + { + m_useCenterKey = string_parse_vector3(value, m_center); + if (!m_useCenterKey) { + m_center = Vector3(0, 0, 0); + } + SceneChangeNotify(); + } + + typedef MemberCaller LightCenterChangedCaller; +}; + +class RenderLightRadiiWire : public OpenGLRenderable { + LightRadii &m_radii; + const Vector3 &m_origin; +public: + RenderLightRadiiWire(LightRadii &radii, const Vector3 &origin) : m_radii(radii), m_origin(origin) + { + } + + void render(RenderStateFlags state) const + { + light_draw_radius_wire(m_origin, m_radii.m_radii); + } +}; + +class RenderLightRadiiFill : public OpenGLRenderable { + LightRadii &m_radii; + const Vector3 &m_origin; +public: + static Shader *m_state; + + RenderLightRadiiFill(LightRadii &radii, const Vector3 &origin) : m_radii(radii), m_origin(origin) + { + } + + void render(RenderStateFlags state) const + { + light_draw_radius_fill(m_origin, m_radii.m_radii); + } +}; + +class RenderLightRadiiBox : public OpenGLRenderable { + const Vector3 &m_origin; +public: + mutable Vector3 m_points[8]; + static Shader *m_state; + + RenderLightRadiiBox(const Vector3 &origin) : m_origin(origin) + { + } + + void render(RenderStateFlags state) const + { + //draw the bounding box of light based on light_radius key + if ((state & RENDER_FILL) != 0) { + aabb_draw_flatshade(m_points); + } else { + aabb_draw_wire(m_points); + } + +#if 1 //disable if you dont want lines going from the center of the light bbox to the corners + light_draw_box_lines(m_origin, m_points); +#endif + } +}; + +Shader *RenderLightRadiiFill::m_state = 0; + +class RenderLightCenter : public OpenGLRenderable { + const Vector3 &m_center; + EntityClass &m_eclass; +public: + static Shader *m_state; + + RenderLightCenter(const Vector3 ¢er, EntityClass &eclass) : m_center(center), m_eclass(eclass) + { + } + + void render(RenderStateFlags state) const + { + glBegin(GL_POINTS); + glColor3fv(vector3_to_array(m_eclass.color)); + glVertex3fv(vector3_to_array(m_center)); + glEnd(); + } +}; + +Shader *RenderLightCenter::m_state = 0; + +class RenderLightProjection : public OpenGLRenderable { + const Matrix4 &m_projection; +public: + + RenderLightProjection(const Matrix4 &projection) : m_projection(projection) + { + } + + void render(RenderStateFlags state) const + { + Matrix4 unproject(matrix4_full_inverse(m_projection)); + Vector3 points[8]; + aabb_corners(AABB(Vector3(0.5f, 0.5f, 0.5f), Vector3(0.5f, 0.5f, 0.5f)), points); + points[0] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[0], 1))); + points[1] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[1], 1))); + points[2] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[2], 1))); + points[3] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[3], 1))); + points[4] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[4], 1))); + points[5] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[5], 1))); + points[6] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[6], 1))); + points[7] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[7], 1))); +// Vector4 test1 = matrix4_transformed_vector4( unproject, Vector4( 0.5f, 0.5f, 0.5f, 1 ) ); +// Vector3 test2 = vector4_projected( test1 ); + aabb_draw_wire(points); + } +}; + +inline void default_extents(Vector3 &extents) +{ + extents = Vector3(8, 8, 8); +} + +class ShaderRef { + CopiedString m_name; + Shader *m_shader; + + void capture() + { + m_shader = GlobalShaderCache().capture(m_name.c_str()); + } + + void release() + { + GlobalShaderCache().release(m_name.c_str()); + } + +public: + ShaderRef() + { + capture(); + } + + ~ShaderRef() + { + release(); + } + + void setName(const char *name) + { + release(); + m_name = name; + capture(); + } + + Shader *get() const + { + return m_shader; + } +}; + +class LightShader { + ShaderRef m_shader; + + void setDefault() + { + m_shader.setName(m_defaultShader); + } + +public: + static const char *m_defaultShader; + + LightShader() + { + setDefault(); + } + + void valueChanged(const char *value) + { + if (string_empty(value)) { + setDefault(); + } else { + m_shader.setName(value); + } + SceneChangeNotify(); + } + + typedef MemberCaller ValueChangedCaller; + + Shader *get() const + { + return m_shader.get(); + } +}; + +const char *LightShader::m_defaultShader = ""; + +inline const BasicVector4 &plane3_to_vector4(const Plane3 &self) +{ + return reinterpret_cast &>( self ); +} + +inline BasicVector4 &plane3_to_vector4(Plane3 &self) +{ + return reinterpret_cast &>( self ); +} + +inline Matrix4 matrix4_from_planes(const Plane3 &left, const Plane3 &right, const Plane3 &bottom, const Plane3 &top, + const Plane3 &front, const Plane3 &back) +{ + return Matrix4( + (right.a - left.a) / 2, + (top.a - bottom.a) / 2, + (back.a - front.a) / 2, + right.a - (right.a - left.a) / 2, + (right.b - left.b) / 2, + (top.b - bottom.b) / 2, + (back.b - front.b) / 2, + right.b - (right.b - left.b) / 2, + (right.c - left.c) / 2, + (top.c - bottom.c) / 2, + (back.c - front.c) / 2, + right.c - (right.c - left.c) / 2, + (right.d - left.d) / 2, + (top.d - bottom.d) / 2, + (back.d - front.d) / 2, + right.d - (right.d - left.d) / 2 + ); +} + +class Light : + public OpenGLRenderable, + public Cullable, + public Bounded, + public Editable, + public Snappable { + EntityKeyValues m_entity; + KeyObserverMap m_keyObservers; + TraversableNodeSet m_traverse; + IdentityTransform m_transform; + + OriginKey m_originKey; + RotationKey m_rotationKey; + Float9 m_rotation; + Colour m_colour; + + ClassnameFilter m_filter; + NamedEntity m_named; + NameKeys m_nameKeys; + TraversableObserverPairRelay m_traverseObservers; + + LightRadii m_radii; + LightRadius m_radius; + + RenderLightRadiiWire m_radii_wire; + RenderLightRadiiFill m_radii_fill; + RenderLightRadiiBox m_radii_box; + RenderLightCenter m_render_center; + RenderableNamedEntity m_renderName; + + Vector3 m_lightOrigin; + bool m_useLightOrigin; + Float9 m_lightRotation; + bool m_useLightRotation; + + Vector3 m_lightTarget; + bool m_useLightTarget; + Vector3 m_lightUp; + bool m_useLightUp; + Vector3 m_lightRight; + bool m_useLightRight; + Vector3 m_lightStart; + bool m_useLightStart; + Vector3 m_lightEnd; + bool m_useLightEnd; + + mutable AABB m_doom3AABB; + mutable Matrix4 m_doom3Rotation; + mutable Matrix4 m_doom3Projection; + mutable Frustum m_doom3Frustum; + mutable bool m_doom3ProjectionChanged; + + RenderLightProjection m_renderProjection; + + LightShader m_shader; + + AABB m_aabb_light; + + Callback m_transformChanged; + Callback m_boundsChanged; + Callback m_evaluateTransform; + + void construct() + { + default_rotation(m_rotation); + m_aabb_light.origin = Vector3(0, 0, 0); + default_extents(m_aabb_light.extents); + + m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter)); + m_keyObservers.insert(Static::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named)); + m_keyObservers.insert("_color", Colour::ColourChangedCaller(m_colour)); + m_keyObservers.insert("_color255", Colour::Colour255ChangedCaller(m_colour)); + m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey)); + m_keyObservers.insert("_light", LightRadii::PrimaryIntensityChangedCaller(m_radii)); + m_keyObservers.insert("light", LightRadii::SecondaryIntensityChangedCaller(m_radii)); + m_keyObservers.insert("fade", LightRadii::FadeChangedCaller(m_radii)); + m_keyObservers.insert("radius", LightRadii::ScaleChangedCaller(m_radii)); + m_keyObservers.insert("scale", LightRadii::ScaleChangedCaller(m_radii)); + m_keyObservers.insert("spawnflags", LightRadii::FlagsChangedCaller(m_radii)); + + } + + void destroy() + { + + } + +// vc 2k5 compiler fix +#if _MSC_VER >= 1400 + public: +#endif + + void updateOrigin() + { + m_boundsChanged(); + m_radius.m_changed(); + GlobalSelectionSystem().pivotChanged(); + } + + void originChanged() + { + m_aabb_light.origin = m_useLightOrigin ? m_lightOrigin : m_originKey.m_origin; + updateOrigin(); + } + + typedef MemberCaller OriginChangedCaller; + + void lightOriginChanged(const char *value) + { + m_useLightOrigin = !string_empty(value); + if (m_useLightOrigin) { + read_origin(m_lightOrigin, value); + } + originChanged(); + } + + typedef MemberCaller LightOriginChangedCaller; + + void lightTargetChanged(const char *value) + { + m_useLightTarget = !string_empty(value); + if (m_useLightTarget) { + read_origin(m_lightTarget, value); + } + projectionChanged(); + } + + typedef MemberCaller LightTargetChangedCaller; + + void lightUpChanged(const char *value) + { + m_useLightUp = !string_empty(value); + if (m_useLightUp) { + read_origin(m_lightUp, value); + } + projectionChanged(); + } + + typedef MemberCaller LightUpChangedCaller; + + void lightRightChanged(const char *value) + { + m_useLightRight = !string_empty(value); + if (m_useLightRight) { + read_origin(m_lightRight, value); + } + projectionChanged(); + } + + typedef MemberCaller LightRightChangedCaller; + + void lightStartChanged(const char *value) + { + m_useLightStart = !string_empty(value); + if (m_useLightStart) { + read_origin(m_lightStart, value); + } + projectionChanged(); + } + + typedef MemberCaller LightStartChangedCaller; + + void lightEndChanged(const char *value) + { + m_useLightEnd = !string_empty(value); + if (m_useLightEnd) { + read_origin(m_lightEnd, value); + } + projectionChanged(); + } + + typedef MemberCaller LightEndChangedCaller; + + void writeLightOrigin() + { + write_origin(m_lightOrigin, &m_entity, "light_origin"); + } + + void updateLightRadiiBox() const + { + const Matrix4 &rotation = rotation_toMatrix(m_rotation); + aabb_corners(AABB(Vector3(0, 0, 0), m_radius.m_radiusTransformed), m_radii_box.m_points); + matrix4_transform_point(rotation, m_radii_box.m_points[0]); + vector3_add(m_radii_box.m_points[0], m_aabb_light.origin); + matrix4_transform_point(rotation, m_radii_box.m_points[1]); + vector3_add(m_radii_box.m_points[1], m_aabb_light.origin); + matrix4_transform_point(rotation, m_radii_box.m_points[2]); + vector3_add(m_radii_box.m_points[2], m_aabb_light.origin); + matrix4_transform_point(rotation, m_radii_box.m_points[3]); + vector3_add(m_radii_box.m_points[3], m_aabb_light.origin); + matrix4_transform_point(rotation, m_radii_box.m_points[4]); + vector3_add(m_radii_box.m_points[4], m_aabb_light.origin); + matrix4_transform_point(rotation, m_radii_box.m_points[5]); + vector3_add(m_radii_box.m_points[5], m_aabb_light.origin); + matrix4_transform_point(rotation, m_radii_box.m_points[6]); + vector3_add(m_radii_box.m_points[6], m_aabb_light.origin); + matrix4_transform_point(rotation, m_radii_box.m_points[7]); + vector3_add(m_radii_box.m_points[7], m_aabb_light.origin); + } + + void rotationChanged() + { + rotation_assign(m_rotation, m_useLightRotation ? m_lightRotation : m_rotationKey.m_rotation); + GlobalSelectionSystem().pivotChanged(); + } + + typedef MemberCaller RotationChangedCaller; + + void lightRotationChanged(const char *value) + { + m_useLightRotation = !string_empty(value); + if (m_useLightRotation) { + read_rotation(m_lightRotation, value); + } + rotationChanged(); + } + + typedef MemberCaller LightRotationChangedCaller; + +public: + + Light(EntityClass *eclass, scene::Node &node, const Callback &transformChanged, + const Callback &boundsChanged, const Callback &evaluateTransform) : + m_entity(eclass), + m_originKey(OriginChangedCaller(*this)), + m_rotationKey(RotationChangedCaller(*this)), + m_colour(Callback()), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_radius(EntityClass_valueForKey(m_entity.getEntityClass(), "light_radius")), + m_radii_wire(m_radii, m_aabb_light.origin), + m_radii_fill(m_radii, m_aabb_light.origin), + m_radii_box(m_aabb_light.origin), + m_render_center(m_radius.m_center, m_entity.getEntityClass()), + m_renderName(m_named, m_aabb_light.origin), + m_useLightOrigin(false), + m_useLightRotation(false), + m_renderProjection(m_doom3Projection), + m_transformChanged(transformChanged), + m_boundsChanged(boundsChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + Light(const Light &other, scene::Node &node, const Callback &transformChanged, + const Callback &boundsChanged, const Callback &evaluateTransform) : + m_entity(other.m_entity), + m_originKey(OriginChangedCaller(*this)), + m_rotationKey(RotationChangedCaller(*this)), + m_colour(Callback()), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_radius(EntityClass_valueForKey(m_entity.getEntityClass(), "light_radius")), + m_radii_wire(m_radii, m_aabb_light.origin), + m_radii_fill(m_radii, m_aabb_light.origin), + m_radii_box(m_aabb_light.origin), + m_render_center(m_radius.m_center, m_entity.getEntityClass()), + m_renderName(m_named, m_aabb_light.origin), + m_useLightOrigin(false), + m_useLightRotation(false), + m_renderProjection(m_doom3Projection), + m_transformChanged(transformChanged), + m_boundsChanged(boundsChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + ~Light() + { + destroy(); + } + + InstanceCounter m_instanceCounter; + + void instanceAttach(const scene::Path &path) + { + if (++m_instanceCounter.m_count == 1) { + m_filter.instanceAttach(); + m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_entity.attach(m_keyObservers); + } + } + + void instanceDetach(const scene::Path &path) + { + if (--m_instanceCounter.m_count == 0) { + m_entity.detach(m_keyObservers); + m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_filter.instanceDetach(); + } + } + + EntityKeyValues &getEntity() + { + return m_entity; + } + + const EntityKeyValues &getEntity() const + { + return m_entity; + } + + scene::Traversable &getTraversable() + { + return m_traverse; + } + + Namespaced &getNamespaced() + { + return m_nameKeys; + } + + Nameable &getNameable() + { + return m_named; + } + + TransformNode &getTransformNode() + { + return m_transform; + } + + void attach(scene::Traversable::Observer *observer) + { + m_traverseObservers.attach(*observer); + } + + void detach(scene::Traversable::Observer *observer) + { + m_traverseObservers.detach(*observer); + } + + void render(RenderStateFlags state) const + { + if (!g_newLightDraw) { + aabb_draw(m_aabb_light, state); + } else { + light_draw(m_aabb_light, state); + } + } + + VolumeIntersectionValue intersectVolume(const VolumeTest &volume, const Matrix4 &localToWorld) const + { + return volume.TestAABB(m_aabb_light, localToWorld); + } + +// cache + const AABB &localAABB() const + { + return m_aabb_light; + } + + + mutable Matrix4 m_projectionOrientation; + + void renderSolid(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + renderer.SetState(m_colour.state(), Renderer::eFullMaterials); + renderer.addRenderable(*this, localToWorld); + + if (selected && g_lightRadii && string_empty(m_entity.getKeyValue("target"))) { + if (renderer.getStyle() == Renderer::eFullMaterials) { + renderer.SetState(RenderLightRadiiFill::m_state, Renderer::eFullMaterials); + renderer.Highlight(Renderer::ePrimitive, false); + renderer.addRenderable(m_radii_fill, localToWorld); + } else { + renderer.addRenderable(m_radii_wire, localToWorld); + } + } + + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + renderSolid(renderer, volume, localToWorld, selected); + if (g_showNames) { + renderer.addRenderable(m_renderName, localToWorld); + } + } + + void testSelect(Selector &selector, SelectionTest &test, const Matrix4 &localToWorld) + { + test.BeginMesh(localToWorld); + + SelectionIntersection best; + aabb_testselect(m_aabb_light, test, best); + if (best.valid()) { + selector.addIntersection(best); + } + } + + void translate(const Vector3 &translation) + { + m_aabb_light.origin = origin_translated(m_aabb_light.origin, translation); + } + + void rotate(const Quaternion &rotation) + { + rotation_rotate(m_rotation, rotation); + } + + void snapto(float snap) + { + if (m_useLightOrigin) { + m_lightOrigin = origin_snapped(m_lightOrigin, snap); + writeLightOrigin(); + } else { + m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap); + m_originKey.write(&m_entity); + } + } + + void setLightRadius(const AABB &aabb) + { + m_aabb_light.origin = aabb.origin; + m_radius.m_radiusTransformed = aabb.extents; + } + + void transformLightRadius(const Matrix4 &transform) + { + matrix4_transform_point(transform, m_aabb_light.origin); + } + + void revertTransform() + { + m_aabb_light.origin = m_useLightOrigin ? m_lightOrigin : m_originKey.m_origin; + rotation_assign(m_rotation, m_useLightRotation ? m_lightRotation : m_rotationKey.m_rotation); + m_radius.m_radiusTransformed = m_radius.m_radius; + } + + void freezeTransform() + { + if (m_useLightOrigin) { + m_lightOrigin = m_aabb_light.origin; + writeLightOrigin(); + } else { + m_originKey.m_origin = m_aabb_light.origin; + m_originKey.write(&m_entity); + } + } + + void transformChanged() + { + revertTransform(); + m_evaluateTransform(); + updateOrigin(); + } + + typedef MemberCaller TransformChangedCaller; + + mutable Matrix4 m_localPivot; + + const Matrix4 &getLocalPivot() const + { + m_localPivot = rotation_toMatrix(m_rotation); + vector4_to_vector3(m_localPivot.t()) = m_aabb_light.origin; + return m_localPivot; + } + + void setLightChangedCallback(const Callback &callback) + { + m_radius.m_changed = callback; + } + + const AABB &aabb() const + { + m_doom3AABB = AABB(m_aabb_light.origin, m_radius.m_radiusTransformed); + return m_doom3AABB; + } + + bool testAABB(const AABB &other) const + { + if (isProjected()) { + Matrix4 transform = rotation(); + vector4_to_vector3(transform.t()) = localAABB().origin; + projection(); + Frustum frustum(frustum_transformed(m_doom3Frustum, transform)); + return frustum_test_aabb(frustum, other) != c_volumeOutside; + } + // test against an AABB which contains the rotated bounds of this light. + const AABB &bounds = aabb(); + return aabb_intersects_aabb(other, AABB( + bounds.origin, + Vector3( + static_cast( fabs(m_rotation[0] * bounds.extents[0]) + + fabs(m_rotation[3] * bounds.extents[1]) + + fabs(m_rotation[6] * bounds.extents[2])), + static_cast( fabs(m_rotation[1] * bounds.extents[0]) + + fabs(m_rotation[4] * bounds.extents[1]) + + fabs(m_rotation[7] * bounds.extents[2])), + static_cast( fabs(m_rotation[2] * bounds.extents[0]) + + fabs(m_rotation[5] * bounds.extents[1]) + + fabs(m_rotation[8] * bounds.extents[2])) + ) + )); + } + + const Matrix4 &rotation() const + { + m_doom3Rotation = rotation_toMatrix(m_rotation); + return m_doom3Rotation; + } + + const Vector3 &offset() const + { + return m_radius.m_center; + } + + const Vector3 &colour() const + { + return m_colour.m_colour; + } + + bool isProjected() const + { + return m_useLightTarget && m_useLightUp && m_useLightRight; + } + + void projectionChanged() + { + m_doom3ProjectionChanged = true; + m_radius.m_changed(); + SceneChangeNotify(); + } + + const Matrix4 &projection() const + { + if (!m_doom3ProjectionChanged) { + return m_doom3Projection; + } + m_doom3ProjectionChanged = false; + m_doom3Projection = g_matrix4_identity; + matrix4_translate_by_vec3(m_doom3Projection, Vector3(0.5f, 0.5f, 0)); + matrix4_scale_by_vec3(m_doom3Projection, Vector3(0.5f, 0.5f, 1)); + +#if 0 + Vector3 right = vector3_cross( m_lightUp, vector3_normalised( m_lightTarget ) ); + Vector3 up = vector3_cross( vector3_normalised( m_lightTarget ), m_lightRight ); + Vector3 target = m_lightTarget; + Matrix4 test( + -right.x(), -right.y(), -right.z(), 0, + -up.x(), -up.y(), -up.z(), 0, + -target.x(), -target.y(), -target.z(), 0, + 0, 0, 0, 1 + ); + Matrix4 frustum = matrix4_frustum( -0.01, 0.01, -0.01, 0.01, 0.01, 1.0 ); + test = matrix4_full_inverse( test ); + matrix4_premultiply_by_matrix4( test, frustum ); + matrix4_multiply_by_matrix4( m_doom3Projection, test ); +#elif 0 + const float nearFar = 1 / 49.5f; + Vector3 right = vector3_cross( m_lightUp, vector3_normalised( m_lightTarget + m_lightRight ) ); + Vector3 up = vector3_cross( vector3_normalised( m_lightTarget + m_lightUp ), m_lightRight ); + Vector3 target = vector3_negated( m_lightTarget * ( 1 + nearFar ) ); + float scale = -1 / vector3_length( m_lightTarget ); + Matrix4 test( + -inverse( right.x() ), -inverse( up.x() ), -inverse( target.x() ), 0, + -inverse( right.y() ), -inverse( up.y() ), -inverse( target.y() ), 0, + -inverse( right.z() ), -inverse( up.z() ), -inverse( target.z() ), scale, + 0, 0, -nearFar, 0 + ); + matrix4_multiply_by_matrix4( m_doom3Projection, test ); +#elif 0 + Vector3 leftA( m_lightTarget - m_lightRight ); + Vector3 leftB( m_lightRight + m_lightUp ); + Plane3 left( vector3_normalised( vector3_cross( leftA, leftB ) ) * ( 1.0 / 128 ), 0 ); + Vector3 rightA( m_lightTarget + m_lightRight ); + Vector3 rightB( vector3_cross( rightA, m_lightTarget ) ); + Plane3 right( vector3_normalised( vector3_cross( rightA, rightB ) ) * ( 1.0 / 128 ), 0 ); + Vector3 bottomA( m_lightTarget - m_lightUp ); + Vector3 bottomB( vector3_cross( bottomA, m_lightTarget ) ); + Plane3 bottom( vector3_normalised( vector3_cross( bottomA, bottomB ) ) * ( 1.0 / 128 ), 0 ); + Vector3 topA( m_lightTarget + m_lightUp ); + Vector3 topB( vector3_cross( topA, m_lightTarget ) ); + Plane3 top( vector3_normalised( vector3_cross( topA, topB ) ) * ( 1.0 / 128 ), 0 ); + Plane3 front( vector3_normalised( m_lightTarget ) * ( 1.0 / 128 ), 1 ); + Plane3 back( vector3_normalised( vector3_negated( m_lightTarget ) ) * ( 1.0 / 128 ), 0 ); + Matrix4 test( matrix4_from_planes( plane3_flipped( left ), plane3_flipped( right ), plane3_flipped( bottom ), plane3_flipped( top ), plane3_flipped( front ), plane3_flipped( back ) ) ); + matrix4_multiply_by_matrix4( m_doom3Projection, test ); +#else + + Plane3 lightProject[4]; + + Vector3 start = m_useLightStart && m_useLightEnd ? m_lightStart : vector3_normalised(m_lightTarget); + Vector3 stop = m_useLightStart && m_useLightEnd ? m_lightEnd : m_lightTarget; + + float rLen = vector3_length(m_lightRight); + Vector3 right = vector3_divided(m_lightRight, rLen); + float uLen = vector3_length(m_lightUp); + Vector3 up = vector3_divided(m_lightUp, uLen); + Vector3 normal = vector3_normalised(vector3_cross(up, right)); + + float dist = vector3_dot(m_lightTarget, normal); + if (dist < 0) { + dist = -dist; + normal = vector3_negated(normal); + } + + right *= (0.5f * dist) / rLen; + up *= -(0.5f * dist) / uLen; + + lightProject[2] = Plane3(normal, 0); + lightProject[0] = Plane3(right, 0); + lightProject[1] = Plane3(up, 0); + + // now offset to center + Vector4 targetGlobal(m_lightTarget, 1); + { + float a = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[0])); + float b = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[2])); + float ofs = 0.5f - a / b; + plane3_to_vector4(lightProject[0]) += plane3_to_vector4(lightProject[2]) * ofs; + } + { + float a = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[1])); + float b = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[2])); + float ofs = 0.5f - a / b; + plane3_to_vector4(lightProject[1]) += plane3_to_vector4(lightProject[2]) * ofs; + } + + // set the falloff vector + Vector3 falloff = stop - start; + float length = vector3_length(falloff); + falloff = vector3_divided(falloff, length); + if (length <= 0) { + length = 1; + } + falloff *= (1.0f / length); + lightProject[3] = Plane3(falloff, -vector3_dot(start, falloff)); + + // we want the planes of s=0, s=q, t=0, and t=q + m_doom3Frustum.left = lightProject[0]; + m_doom3Frustum.bottom = lightProject[1]; + m_doom3Frustum.right = Plane3(lightProject[2].normal() - lightProject[0].normal(), + lightProject[2].dist() - lightProject[0].dist()); + m_doom3Frustum.top = Plane3(lightProject[2].normal() - lightProject[1].normal(), + lightProject[2].dist() - lightProject[1].dist()); + + // we want the planes of s=0 and s=1 for front and rear clipping planes + m_doom3Frustum.front = lightProject[3]; + + m_doom3Frustum.back = lightProject[3]; + m_doom3Frustum.back.dist() -= 1.0f; + m_doom3Frustum.back = plane3_flipped(m_doom3Frustum.back); + + Matrix4 test(matrix4_from_planes(m_doom3Frustum.left, m_doom3Frustum.right, m_doom3Frustum.bottom, + m_doom3Frustum.top, m_doom3Frustum.front, m_doom3Frustum.back)); + matrix4_multiply_by_matrix4(m_doom3Projection, test); + + m_doom3Frustum.left = plane3_normalised(m_doom3Frustum.left); + m_doom3Frustum.right = plane3_normalised(m_doom3Frustum.right); + m_doom3Frustum.bottom = plane3_normalised(m_doom3Frustum.bottom); + m_doom3Frustum.top = plane3_normalised(m_doom3Frustum.top); + m_doom3Frustum.back = plane3_normalised(m_doom3Frustum.back); + m_doom3Frustum.front = plane3_normalised(m_doom3Frustum.front); +#endif + //matrix4_scale_by_vec3(m_doom3Projection, Vector3(1.0 / 128, 1.0 / 128, 1.0 / 128)); + return m_doom3Projection; + } + + Shader *getShader() const + { + return m_shader.get(); + } +}; + +class LightInstance : + public TargetableInstance, + public TransformModifier, + public Renderable, + public SelectionTestable, + public RendererLight, + public PlaneSelectable, + public ComponentSelectionTestable { + class TypeCasts { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + m_casts = TargetableInstance::StaticTypeCasts::instance().get(); + 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); + InstanceIdentityCast::install(m_casts); + } + + InstanceTypeCastTable &get() + { + return m_casts; + } + }; + + Light &m_contained; + DragPlanes m_dragPlanes; // dragplanes for lightresizing using mousedrag +public: + typedef LazyStatic StaticTypeCasts; + + Bounded &get(NullType) + { + return m_contained; + } + + STRING_CONSTANT(Name, "LightInstance"); + + LightInstance(const scene::Path &path, scene::Instance *parent, Light &contained) : + TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this), + TransformModifier(Light::TransformChangedCaller(contained), ApplyTransformCaller(*this)), + m_contained(contained), + m_dragPlanes(SelectedChangedComponentCaller(*this)) + { + m_contained.instanceAttach(Instance::path()); + StaticRenderableConnectionLines::instance().attach(*this); + } + + ~LightInstance() + { + StaticRenderableConnectionLines::instance().detach(*this); + m_contained.instanceDetach(Instance::path()); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderSolid(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void testSelect(Selector &selector, SelectionTest &test) + { + m_contained.testSelect(selector, test, Instance::localToWorld()); + } + + void selectPlanes(Selector &selector, SelectionTest &test, const PlaneCallback &selectedPlaneCallback) + { + test.BeginMesh(localToWorld()); + m_dragPlanes.selectPlanes(m_contained.aabb(), selector, test, selectedPlaneCallback, rotation()); + } + + void selectReversedPlanes(Selector &selector, const SelectedPlanes &selectedPlanes) + { + m_dragPlanes.selectReversedPlanes(m_contained.aabb(), selector, selectedPlanes, rotation()); + } + + bool isSelectedComponents() const + { + return m_dragPlanes.isSelected(); + } + + void setSelectedComponents(bool select, SelectionSystem::EComponentMode mode) + { + if (mode == SelectionSystem::eFace) { + m_dragPlanes.setSelected(false); + } + } + + void testSelectComponents(Selector &selector, SelectionTest &test, SelectionSystem::EComponentMode mode) + { + } + + void selectedChangedComponent(const Selectable &selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable); + GlobalSelectionSystem().onComponentSelection(*this, selectable); + } + + typedef MemberCaller SelectedChangedComponentCaller; + + void evaluateTransform() + { + if (getType() == TRANSFORM_PRIMITIVE) { + m_contained.translate(getTranslation()); + m_contained.rotate(getRotation()); + } else { + //globalOutputStream() << getTranslation() << "\n"; + + m_dragPlanes.m_bounds = m_contained.aabb(); + m_contained.setLightRadius(m_dragPlanes.evaluateResize(getTranslation(), rotation())); + } + } + + void applyTransform() + { + m_contained.revertTransform(); + evaluateTransform(); + m_contained.freezeTransform(); + } + + typedef MemberCaller ApplyTransformCaller; + + void lightChanged() + { + GlobalShaderCache().changed(*this); + } + + typedef MemberCaller LightChangedCaller; + + Shader *getShader() const + { + return m_contained.getShader(); + } + + const AABB &aabb() const + { + return m_contained.aabb(); + } + + bool testAABB(const AABB &other) const + { + return m_contained.testAABB(other); + } + + const Matrix4 &rotation() const + { + return m_contained.rotation(); + } + + const Vector3 &offset() const + { + return m_contained.offset(); + } + + const Vector3 &colour() const + { + return m_contained.colour(); + } + + bool isProjected() const + { + return m_contained.isProjected(); + } + + const Matrix4 &projection() const + { + return m_contained.projection(); + } +}; + +class LightNode : + public scene::Node::Symbiot, + public scene::Instantiable, + public scene::Cloneable, + public scene::Traversable::Observer { + 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); + } + + NodeTypeCastTable &get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + Light m_contained; + + void construct() + { + } + + void destroy() + { + + } + +public: + typedef LazyStatic StaticTypeCasts; + + scene::Traversable &get(NullType) + { + return m_contained.getTraversable(); + } + + Editable &get(NullType) + { + return m_contained; + } + + Snappable &get(NullType) + { + return m_contained; + } + + TransformNode &get(NullType) + { + return m_contained.getTransformNode(); + } + + Entity &get(NullType) + { + return m_contained.getEntity(); + } + + Nameable &get(NullType) + { + return m_contained.getNameable(); + } + + Namespaced &get(NullType) + { + return m_contained.getNamespaced(); + } + + LightNode(EntityClass *eclass) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSet::BoundsChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + LightNode(const LightNode &other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + scene::Traversable::Observer(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSet::BoundsChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + ~LightNode() + { + destroy(); + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + scene::Node &clone() const + { + return (new LightNode(*this))->node(); + } + + void insert(scene::Node &child) + { + m_instances.insert(child); + } + + void erase(scene::Node &child) + { + m_instances.erase(child); + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new LightInstance(path, parent, m_contained); + } + + 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); + } +}; + +void Light_Construct() +{ + RenderLightRadiiFill::m_state = GlobalShaderCache().capture("$Q3MAP2_LIGHT_SPHERE"); + RenderLightCenter::m_state = GlobalShaderCache().capture("$BIGPOINT"); +} + +void Light_Destroy() +{ + GlobalShaderCache().release("$Q3MAP2_LIGHT_SPHERE"); + GlobalShaderCache().release("$BIGPOINT"); +} + +scene::Node &New_Light(EntityClass *eclass) +{ + return (new LightNode(eclass))->node(); +} diff --git a/plugins/entity/light.h b/plugins/entity/light.h new file mode 100644 index 0000000..ade4670 --- /dev/null +++ b/plugins/entity/light.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_LIGHT_H ) +#define INCLUDED_LIGHT_H + +namespace scene { + class Node; +} +class EntityClass; + +scene::Node &New_Light(EntityClass *eclass); + +void Light_Construct(); + +void Light_Destroy(); + +#endif diff --git a/plugins/entity/miscmodel.cpp b/plugins/entity/miscmodel.cpp new file mode 100644 index 0000000..3bfa5e8 --- /dev/null +++ b/plugins/entity/miscmodel.cpp @@ -0,0 +1,513 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 Represents the Quake3 misc_model entity. +/// +/// This entity displays the model specified in its "model" key. +/// The "origin", "angles" and "modelscale*" keys directly control the entity's local-to-parent transform. + +#include "cullable.h" +#include "renderable.h" +#include "editable.h" + +#include "selectionlib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "traverselib.h" +#include "entitylib.h" +#include "eclasslib.h" +#include "render.h" +#include "pivot.h" + +#include "targetable.h" +#include "origin.h" +#include "angles.h" +#include "scale.h" +#include "model.h" +#include "filters.h" +#include "namedentity.h" +#include "keyobservers.h" +#include "namekeys.h" + +#include "entity.h" + +class PropStatic : + public Snappable { + EntityKeyValues m_entity; + KeyObserverMap m_keyObservers; + MatrixTransform m_transform; + + OriginKey m_originKey; + Vector3 m_origin; + AnglesKey m_anglesKey; + Vector3 m_angles; + ScaleKey m_scaleKey; + Vector3 m_scale; + + SingletonModel m_model; + + ClassnameFilter m_filter; + NamedEntity m_named; + NameKeys m_nameKeys; + RenderablePivot m_renderOrigin; + RenderableNamedEntity m_renderName; + + Callback m_transformChanged; + Callback m_evaluateTransform; + + void construct() + { + m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter)); + m_keyObservers.insert(Static::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named)); + m_keyObservers.insert("model", SingletonModel::ModelChangedCaller(m_model)); + m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey)); + m_keyObservers.insert("angle", AnglesKey::AngleChangedCaller(m_anglesKey)); + m_keyObservers.insert("angles", AnglesKey::AnglesChangedCaller(m_anglesKey)); + m_keyObservers.insert("modelscale", ScaleKey::UniformScaleChangedCaller(m_scaleKey)); + m_keyObservers.insert("modelscale_vec", ScaleKey::ScaleChangedCaller(m_scaleKey)); + } + + void updateTransform() + { + m_transform.localToParent() = g_matrix4_identity; + matrix4_transform_by_euler_xyz_degrees(m_transform.localToParent(), m_origin, m_angles, m_scale); + m_transformChanged(); + } + +// vc 2k5 compiler fix +#if _MSC_VER >= 1400 + public: +#endif + + void originChanged() + { + m_origin = m_originKey.m_origin; + updateTransform(); + } + + typedef MemberCaller OriginChangedCaller; + + void anglesChanged() + { + m_angles = m_anglesKey.m_angles; + updateTransform(); + } + + typedef MemberCaller AnglesChangedCaller; + + void scaleChanged() + { + m_scale = m_scaleKey.m_scale; + updateTransform(); + } + + typedef MemberCaller ScaleChangedCaller; +public: + + PropStatic(EntityClass *eclass, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(eclass), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_scaleKey(ScaleChangedCaller(*this)), + m_scale(SCALEKEY_IDENTITY), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_renderName(m_named, g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + PropStatic(const PropStatic &other, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(other.m_entity), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_scaleKey(ScaleChangedCaller(*this)), + m_scale(SCALEKEY_IDENTITY), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_renderName(m_named, g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + InstanceCounter m_instanceCounter; + + void instanceAttach(const scene::Path &path) + { + if (++m_instanceCounter.m_count == 1) { + m_filter.instanceAttach(); + m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_entity.attach(m_keyObservers); + } + } + + void instanceDetach(const scene::Path &path) + { + if (--m_instanceCounter.m_count == 0) { + m_entity.detach(m_keyObservers); + m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_filter.instanceDetach(); + } + } + + EntityKeyValues &getEntity() + { + return m_entity; + } + + const EntityKeyValues &getEntity() const + { + return m_entity; + } + + scene::Traversable &getTraversable() + { + return m_model.getTraversable(); + } + + Namespaced &getNamespaced() + { + return m_nameKeys; + } + + Nameable &getNameable() + { + return m_named; + } + + TransformNode &getTransformNode() + { + return m_transform; + } + + void attach(scene::Traversable::Observer *observer) + { + m_model.attach(observer); + } + + void detach(scene::Traversable::Observer *observer) + { + m_model.detach(observer); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + if (selected) { + m_renderOrigin.render(renderer, volume, localToWorld); + } + + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + renderSolid(renderer, volume, localToWorld, selected); + if (g_showNames) { + renderer.addRenderable(m_renderName, localToWorld); + } + } + + void translate(const Vector3 &translation) + { + m_origin = origin_translated(m_origin, translation); + } + + void rotate(const Quaternion &rotation) + { + m_angles = angles_rotated(m_angles, rotation); + } + + void scale(const Vector3 &scaling) + { + m_scale = scale_scaled(m_scale, scaling); + } + + void snapto(float snap) + { + m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap); + m_originKey.write(&m_entity); + } + + void revertTransform() + { + m_origin = m_originKey.m_origin; + m_angles = m_anglesKey.m_angles; + m_scale = m_scaleKey.m_scale; + } + + void freezeTransform() + { + m_originKey.m_origin = m_origin; + m_originKey.write(&m_entity); + m_anglesKey.m_angles = m_angles; + m_anglesKey.write(&m_entity); + m_scaleKey.m_scale = m_scale; + m_scaleKey.write(&m_entity); + } + + void transformChanged() + { + revertTransform(); + m_evaluateTransform(); + updateTransform(); + } + + typedef MemberCaller TransformChangedCaller; +}; + +class PropStaticInstance : public TargetableInstance, public TransformModifier, public Renderable { + class TypeCasts { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + m_casts = TargetableInstance::StaticTypeCasts::instance().get(); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceIdentityCast::install(m_casts); + } + + InstanceTypeCastTable &get() + { + return m_casts; + } + }; + + PropStatic &m_contained; +public: + typedef LazyStatic StaticTypeCasts; + + STRING_CONSTANT(Name, "PropStaticInstance"); + + PropStaticInstance(const scene::Path &path, scene::Instance *parent, PropStatic &miscmodel) : + TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), miscmodel.getEntity(), *this), + TransformModifier(PropStatic::TransformChangedCaller(miscmodel), ApplyTransformCaller(*this)), + m_contained(miscmodel) + { + m_contained.instanceAttach(Instance::path()); + StaticRenderableConnectionLines::instance().attach(*this); + } + + ~PropStaticInstance() + { + StaticRenderableConnectionLines::instance().detach(*this); + m_contained.instanceDetach(Instance::path()); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderSolid(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void evaluateTransform() + { + if (getType() == TRANSFORM_PRIMITIVE) { + m_contained.translate(getTranslation()); + m_contained.rotate(getRotation()); + m_contained.scale(getScale()); + } + } + + void applyTransform() + { + m_contained.revertTransform(); + evaluateTransform(); + m_contained.freezeTransform(); + } + + typedef MemberCaller ApplyTransformCaller; +}; + +class PropStaticNode : + public scene::Node::Symbiot, + public scene::Instantiable, + public scene::Cloneable, + public scene::Traversable::Observer { + 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); + } + + NodeTypeCastTable &get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + PropStatic m_contained; + + void construct() + { + m_contained.attach(this); + } + + void destroy() + { + m_contained.detach(this); + } + +public: + typedef LazyStatic StaticTypeCasts; + + scene::Traversable &get(NullType) + { + return m_contained.getTraversable(); + } + + Snappable &get(NullType) + { + return m_contained; + } + + TransformNode &get(NullType) + { + return m_contained.getTransformNode(); + } + + Entity &get(NullType) + { + return m_contained.getEntity(); + } + + Nameable &get(NullType) + { + return m_contained.getNameable(); + } + + Namespaced &get(NullType) + { + return m_contained.getNamespaced(); + } + + PropStaticNode(EntityClass *eclass) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + PropStaticNode(const PropStaticNode &other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + scene::Traversable::Observer(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + ~PropStaticNode() + { + destroy(); + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + scene::Node &clone() const + { + return (new PropStaticNode(*this))->node(); + } + + void insert(scene::Node &child) + { + m_instances.insert(child); + } + + void erase(scene::Node &child) + { + m_instances.erase(child); + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new PropStaticInstance(path, parent, m_contained); + } + + 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 &New_PropStatic(EntityClass *eclass) +{ + return (new PropStaticNode(eclass))->node(); +} + +void PropStatic_construct() +{ +} + +void PropStatic_destroy() +{ +} diff --git a/plugins/entity/miscmodel.h b/plugins/entity/miscmodel.h new file mode 100644 index 0000000..7b0befe --- /dev/null +++ b/plugins/entity/miscmodel.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_MISCMODEL_H ) +#define INCLUDED_MISCMODEL_H + +scene::Node &New_PropStatic(EntityClass *eclass); + +void PropStatic_construct(); + +void PropStatic_destroy(); + +#endif diff --git a/plugins/entity/model.h b/plugins/entity/model.h new file mode 100644 index 0000000..5e5dc11 --- /dev/null +++ b/plugins/entity/model.h @@ -0,0 +1,126 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +#include "entitylib.h" +#include "traverselib.h" +#include "generic/callback.h" +#include "stream/stringstream.h" +#include "os/path.h" +#include "moduleobserver.h" + +class EModel : public ModuleObserver { + ResourceReference m_resource; + scene::Traversable &m_traverse; + scene::Node *m_node; + Callback m_modelChanged; + +public: + EModel(scene::Traversable &traversable, const Callback &modelChanged) + : m_resource(""), m_traverse(traversable), m_node(0), m_modelChanged(modelChanged) + { + m_resource.attach(*this); + } + + ~EModel() + { + m_resource.detach(*this); + } + + void realise() + { + m_resource.get()->load(); + m_node = m_resource.get()->getNode(); + if (m_node != 0) { + m_traverse.insert(*m_node); + } + } + + void unrealise() + { + if (m_node != 0) { + m_traverse.erase(*m_node); + } + } + + void modelChanged(const char *value) + { + StringOutputStream cleaned(string_length(value)); + cleaned << PathCleaned(value); + m_resource.detach(*this); + m_resource.setName(cleaned.c_str()); + m_resource.attach(*this); + m_modelChanged(); + } + + typedef MemberCaller ModelChangedCaller; + + const char *getName() const + { + return m_resource.getName(); + } + + scene::Node *getNode() const + { + return m_node; + } +}; + +class SingletonModel { + TraversableNode m_traverse; + EModel m_model; +public: + SingletonModel() + : m_model(m_traverse, Callback()) + { + } + + void attach(scene::Traversable::Observer *observer) + { + m_traverse.attach(observer); + } + + void detach(scene::Traversable::Observer *observer) + { + m_traverse.detach(observer); + } + + scene::Traversable &getTraversable() + { + return m_traverse; + } + + void modelChanged(const char *value) + { + m_model.modelChanged(value); + } + + typedef MemberCaller ModelChangedCaller; + + scene::Node *getNode() const + { + return m_model.getNode(); + } +}; + +#endif diff --git a/plugins/entity/modelskinkey.h b/plugins/entity/modelskinkey.h new file mode 100644 index 0000000..f1c1cca --- /dev/null +++ b/plugins/entity/modelskinkey.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_MODELSKINKEY_H ) +#define INCLUDED_MODELSKINKEY_H + +#include "modelskin.h" + +#include "os/path.h" +#include "stream/stringstream.h" +#include "moduleobserver.h" +#include "entitylib.h" +#include "traverselib.h" + +inline void parseTextureName(CopiedString &name, const char *token) +{ + StringOutputStream cleaned(256); + cleaned << PathCleaned(token); + name = StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str())); // remove extension +} + +class ModelSkinKey : public ModuleObserver { + CopiedString m_name; + ModelSkin *m_skin; + Callback m_skinChangedCallback; + + ModelSkinKey(const ModelSkinKey &); + + ModelSkinKey operator=(const ModelSkinKey &); + + void construct() + { + m_skin = &GlobalModelSkinCache().capture(m_name.c_str()); + m_skin->attach(*this); + } + + void destroy() + { + m_skin->detach(*this); + GlobalModelSkinCache().release(m_name.c_str()); + } + +public: + ModelSkinKey(const Callback &skinChangedCallback) : m_skinChangedCallback(skinChangedCallback) + { + construct(); + } + + ~ModelSkinKey() + { + destroy(); + } + + ModelSkin &get() const + { + return *m_skin; + } + + void skinChanged(const char *value) + { + destroy(); + parseTextureName(m_name, value); + construct(); + } + + typedef MemberCaller SkinChangedCaller; + + void realise() + { + m_skinChangedCallback(); + } + + void unrealise() + { + } +}; + +class InstanceSkinChanged : public scene::Instantiable::Visitor { +public: + void visit(scene::Instance &instance) const + { + //\todo don't do this for instances that are not children of the entity setting the skin + SkinnedModel *skinned = InstanceTypeCast::cast(instance); + if (skinned != 0) { + skinned->skinChanged(); + } + } +}; + +inline void Node_modelSkinChanged(scene::Node &node) +{ + scene::Instantiable *instantiable = Node_getInstantiable(node); + ASSERT_NOTNULL(instantiable); + instantiable->forEachInstance(InstanceSkinChanged()); +} + +#endif diff --git a/plugins/entity/namedentity.h b/plugins/entity/namedentity.h new file mode 100644 index 0000000..33c771a --- /dev/null +++ b/plugins/entity/namedentity.h @@ -0,0 +1,111 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_NAMEDENTITY_H ) +#define INCLUDED_NAMEDENTITY_H + +#include "entitylib.h" +#include "eclasslib.h" +#include "generic/callback.h" +#include "nameable.h" + +#include + +class NameCallbackSet { + typedef std::set NameCallbacks; + NameCallbacks m_callbacks; +public: + void insert(const NameCallback &callback) + { + m_callbacks.insert(callback); + } + + void erase(const NameCallback &callback) + { + m_callbacks.erase(callback); + } + + void changed(const char *name) const + { + for (NameCallbacks::const_iterator i = m_callbacks.begin(); i != m_callbacks.end(); ++i) { + (*i)(name); + } + } +}; + +class NamedEntity : public Nameable { + EntityKeyValues &m_entity; + NameCallbackSet m_changed; + CopiedString m_name; +public: + NamedEntity(EntityKeyValues &entity) : m_entity(entity) + { + } + + const char *name() const + { + if (string_empty(m_name.c_str())) { + return m_entity.getEntityClass().name(); + } + return m_name.c_str(); + } + + void attach(const NameCallback &callback) + { + m_changed.insert(callback); + } + + void detach(const NameCallback &callback) + { + m_changed.erase(callback); + } + + void identifierChanged(const char *value) + { + if (string_empty(value)) { + m_changed.changed(m_entity.getEntityClass().name()); + } else { + m_changed.changed(value); + } + m_name = value; + } + + typedef MemberCaller IdentifierChangedCaller; +}; + +class RenderableNamedEntity : public OpenGLRenderable { + const NamedEntity &m_named; + const Vector3 &m_position; +public: + RenderableNamedEntity(const NamedEntity &named, const Vector3 &position) + : m_named(named), m_position(position) + { + } + + void render(RenderStateFlags state) const + { + glRasterPos3fv(vector3_to_array(m_position)); + GlobalOpenGL().drawString(m_named.name()); + } +}; + + +#endif diff --git a/plugins/entity/namekeys.h b/plugins/entity/namekeys.h new file mode 100644 index 0000000..f298674 --- /dev/null +++ b/plugins/entity/namekeys.h @@ -0,0 +1,156 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_NAMEKEYS_H ) +#define INCLUDED_NAMEKEYS_H + +#include +#include +#include "generic/static.h" +#include "entitylib.h" +#include "namespace.h" + +inline bool string_is_integer(const char *string) +{ + strtol(string, const_cast( &string ), 10); + return *string == '\0'; +} + +typedef bool ( *KeyIsNameFunc )(const char *key); + +class KeyIsName { +public: + KeyIsNameFunc m_keyIsName; + const char *m_nameKey; + + KeyIsName() + { + } +}; + + +typedef MemberCaller KeyValueAssignCaller; +typedef MemberCaller KeyValueAttachCaller; +typedef MemberCaller KeyValueDetachCaller; + +class NameKeys : public Entity::Observer, public Namespaced { + Namespace *m_namespace; + EntityKeyValues &m_entity; + KeyIsNameFunc m_keyIsName; + + NameKeys(const NameKeys &other); + + NameKeys &operator=(const NameKeys &other); + + typedef std::map KeyValues; + KeyValues m_keyValues; + + void insertName(const char *key, EntityKeyValue &value) + { + if (m_namespace != 0 && m_keyIsName(key)) { + //globalOutputStream() << "insert " << key << "\n"; + m_namespace->attach(KeyValueAssignCaller(value), KeyValueAttachCaller(value)); + } + } + + void eraseName(const char *key, EntityKeyValue &value) + { + if (m_namespace != 0 && m_keyIsName(key)) { + //globalOutputStream() << "erase " << key << "\n"; + m_namespace->detach(KeyValueAssignCaller(value), KeyValueDetachCaller(value)); + } + } + + void insertAll() + { + for (KeyValues::iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) { + insertName((*i).first.c_str(), *(*i).second); + } + } + + void eraseAll() + { + for (KeyValues::iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) { + eraseName((*i).first.c_str(), *(*i).second); + } + } + +public: + NameKeys(EntityKeyValues &entity) : m_namespace(0), m_entity(entity), + m_keyIsName(Static::instance().m_keyIsName) + { + m_entity.attach(*this); + } + + ~NameKeys() + { + m_entity.detach(*this); + } + + void setNamespace(Namespace &space) + { + eraseAll(); + m_namespace = &space; + insertAll(); + } + + void setKeyIsName(KeyIsNameFunc keyIsName) + { + eraseAll(); + m_keyIsName = keyIsName; + insertAll(); + } + + void insert(const char *key, EntityKeyValue &value) + { + m_keyValues.insert(KeyValues::value_type(key, &value)); + insertName(key, value); + } + + void erase(const char *key, EntityKeyValue &value) + { + eraseName(key, value); + m_keyValues.erase(key); + } +}; + +inline bool keyIsNameDoom3(const char *key) +{ + return string_equal(key, "target") + || (string_equal_n(key, "target", 6) && string_is_integer(key + 6)) + || string_equal(key, "name"); +} + +inline bool keyIsNameDoom3Doom3Group(const char *key) +{ + return keyIsNameDoom3(key) + || string_equal(key, "model"); +} + +inline bool keyIsNameQuake3(const char *key) +{ + return string_equal(key, "target") + || string_equal(key, "targetname") + || string_equal(key, "killtarget") + || (string_equal_n(key, "target", 6) && string_is_integer(key + 6)); // Nexuiz +} + +#endif diff --git a/plugins/entity/origin.h b/plugins/entity/origin.h new file mode 100644 index 0000000..1259b9b --- /dev/null +++ b/plugins/entity/origin.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_ORIGIN_H ) +#define INCLUDED_ORIGIN_H + +#include "ientity.h" + +#include "math/matrix.h" +#include "generic/callback.h" +#include "stringio.h" + +const Vector3 ORIGINKEY_IDENTITY = Vector3(0, 0, 0); + +inline void default_origin(Vector3 &origin) +{ + origin = ORIGINKEY_IDENTITY; +} + +inline void read_origin(Vector3 &origin, const char *value) +{ + if (!string_parse_vector3(value, origin)) { + default_origin(origin); + } +} + +inline void write_origin(const Vector3 &origin, Entity *entity, const char *key) +{ + char value[64]; + sprintf(value, "%f %f %f", origin[0], origin[1], origin[2]); + entity->setKeyValue(key, value); +} + +inline Vector3 origin_translated(const Vector3 &origin, const Vector3 &translation) +{ + return matrix4_get_translation_vec3( + matrix4_multiplied_by_matrix4( + matrix4_translation_for_vec3(origin), + matrix4_translation_for_vec3(translation) + ) + ); +} + +inline Vector3 origin_snapped(const Vector3 &origin, float snap) +{ + return vector3_snapped(origin, snap); +} + +class OriginKey { + Callback m_originChanged; +public: + Vector3 m_origin; + + + OriginKey(const Callback &originChanged) + : m_originChanged(originChanged), m_origin(ORIGINKEY_IDENTITY) + { + } + + void originChanged(const char *value) + { + read_origin(m_origin, value); + m_originChanged(); + } + + typedef MemberCaller OriginChangedCaller; + + + void write(Entity *entity) const + { + write_origin(m_origin, entity, "origin"); + } +}; + + +#include "scenelib.h" + +inline BrushDoom3 *Node_getBrushDoom3(scene::Node &node) +{ + return NodeTypeCast::cast(node); +} + +inline void BrushDoom3_setDoom3GroupOrigin(scene::Node &node, const Vector3 &origin) +{ + BrushDoom3 *brush = Node_getBrushDoom3(node); + if (brush != 0) { + brush->setDoom3GroupOrigin(origin); + } +} + +class SetDoom3GroupOriginWalker : public scene::Traversable::Walker { + const Vector3 &m_origin; +public: + SetDoom3GroupOriginWalker(const Vector3 &origin) : m_origin(origin) + { + } + + bool pre(scene::Node &node) const + { + BrushDoom3_setDoom3GroupOrigin(node, m_origin); + return true; + } +}; + +class Doom3GroupOrigin : public scene::Traversable::Observer { + scene::Traversable &m_set; + const Vector3 &m_origin; + bool m_enabled; + +public: + Doom3GroupOrigin(scene::Traversable &set, const Vector3 &origin) : m_set(set), m_origin(origin), m_enabled(false) + { + } + + void enable() + { + m_enabled = true; + originChanged(); + } + + void disable() + { + m_enabled = false; + } + + void originChanged() + { + if (m_enabled) { + m_set.traverse(SetDoom3GroupOriginWalker(m_origin)); + } + } + + void insert(scene::Node &node) + { + if (m_enabled) { + BrushDoom3_setDoom3GroupOrigin(node, m_origin); + } + } + + void erase(scene::Node &node) + { + if (m_enabled) { + BrushDoom3_setDoom3GroupOrigin(node, Vector3(0, 0, 0)); + } + } +}; + + +#endif diff --git a/plugins/entity/plugin.cpp b/plugins/entity/plugin.cpp new file mode 100644 index 0000000..5dc0a05 --- /dev/null +++ b/plugins/entity/plugin.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 "debugging/debugging.h" + +#include "iscenegraph.h" +#include "irender.h" +#include "iselection.h" +#include "ientity.h" +#include "iundo.h" +#include "ieclass.h" +#include "igl.h" +#include "ireference.h" +#include "ifilter.h" +#include "preferencesystem.h" +#include "qerplugin.h" +#include "namespace.h" +#include "modelskin.h" + +#include "typesystem.h" + +#include "entity.h" +#include "skincache.h" + +#include "modulesystem/singletonmodule.h" + +class EntityDependencies : + public GlobalRadiantModuleRef, + public GlobalOpenGLModuleRef, + public GlobalUndoModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalSelectionModuleRef, + public GlobalReferenceModuleRef, + public GlobalFilterModuleRef, + public GlobalPreferenceSystemModuleRef, + public GlobalNamespaceModuleRef, + public GlobalModelSkinCacheModuleRef { +}; + +class EntityQ3API : public TypeSystemRef { + EntityCreator *m_entityq3; +public: + typedef EntityCreator Type; + + STRING_CONSTANT(Name, "quake3"); + + EntityQ3API() + { + Entity_Construct(); + + m_entityq3 = &GetEntityCreator(); + + GlobalReferenceCache().setEntityCreator(*m_entityq3); + } + + ~EntityQ3API() + { + Entity_Destroy(); + } + + EntityCreator *getTable() + { + return m_entityq3; + } +}; + +typedef SingletonModule EntityQ3Module; + +EntityQ3Module g_EntityQ3Module; + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer &server) +{ + initialiseModule(server); + + g_EntityQ3Module.selfRegister(); + Doom3ModelSkinCacheModule_selfRegister(server); +} diff --git a/plugins/entity/prop_dynamic.cpp b/plugins/entity/prop_dynamic.cpp new file mode 100644 index 0000000..d5c6e67 --- /dev/null +++ b/plugins/entity/prop_dynamic.cpp @@ -0,0 +1,507 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" +#include "renderable.h" +#include "editable.h" + +#include "selectionlib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "traverselib.h" +#include "entitylib.h" +#include "eclasslib.h" +#include "render.h" +#include "pivot.h" + +#include "targetable.h" +#include "origin.h" +#include "angles.h" +#include "scale.h" +#include "model.h" +#include "filters.h" +#include "namedentity.h" +#include "keyobservers.h" +#include "namekeys.h" + +#include "entity.h" + +class PropDynamic : + public Snappable { + EntityKeyValues m_entity; + KeyObserverMap m_keyObservers; + MatrixTransform m_transform; + + OriginKey m_originKey; + Vector3 m_origin; + AnglesKey m_anglesKey; + Vector3 m_angles; + ScaleKey m_scaleKey; + Vector3 m_scale; + + SingletonModel m_model; + + ClassnameFilter m_filter; + NamedEntity m_named; + NameKeys m_nameKeys; + RenderablePivot m_renderOrigin; + RenderableNamedEntity m_renderName; + + Callback m_transformChanged; + Callback m_evaluateTransform; + + void construct() + { + m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter)); + m_keyObservers.insert(Static::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named)); + m_keyObservers.insert("model", SingletonModel::ModelChangedCaller(m_model)); + m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey)); + m_keyObservers.insert("angle", AnglesKey::AngleChangedCaller(m_anglesKey)); + m_keyObservers.insert("angles", AnglesKey::AnglesChangedCaller(m_anglesKey)); + m_keyObservers.insert("modelscale", ScaleKey::UniformScaleChangedCaller(m_scaleKey)); + m_keyObservers.insert("modelscale_vec", ScaleKey::ScaleChangedCaller(m_scaleKey)); + } + + void updateTransform() + { + m_transform.localToParent() = g_matrix4_identity; + matrix4_transform_by_euler_xyz_degrees(m_transform.localToParent(), m_origin, m_angles, m_scale); + m_transformChanged(); + } + +// vc 2k5 compiler fix +#if _MSC_VER >= 1400 + public: +#endif + + void originChanged() + { + m_origin = m_originKey.m_origin; + updateTransform(); + } + + typedef MemberCaller OriginChangedCaller; + + void anglesChanged() + { + m_angles = m_anglesKey.m_angles; + updateTransform(); + } + + typedef MemberCaller AnglesChangedCaller; + + void scaleChanged() + { + m_scale = m_scaleKey.m_scale; + updateTransform(); + } + + typedef MemberCaller ScaleChangedCaller; +public: + + PropDynamic(EntityClass *eclass, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(eclass), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_scaleKey(ScaleChangedCaller(*this)), + m_scale(SCALEKEY_IDENTITY), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_renderName(m_named, g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + PropDynamic(const PropDynamic &other, scene::Node &node, const Callback &transformChanged, + const Callback &evaluateTransform) : + m_entity(other.m_entity), + m_originKey(OriginChangedCaller(*this)), + m_origin(ORIGINKEY_IDENTITY), + m_anglesKey(AnglesChangedCaller(*this)), + m_angles(ANGLESKEY_IDENTITY), + m_scaleKey(ScaleChangedCaller(*this)), + m_scale(SCALEKEY_IDENTITY), + m_filter(m_entity, node), + m_named(m_entity), + m_nameKeys(m_entity), + m_renderName(m_named, g_vector3_identity), + m_transformChanged(transformChanged), + m_evaluateTransform(evaluateTransform) + { + construct(); + } + + InstanceCounter m_instanceCounter; + + void instanceAttach(const scene::Path &path) + { + if (++m_instanceCounter.m_count == 1) { + m_filter.instanceAttach(); + m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end())); + m_entity.attach(m_keyObservers); + } + } + + void instanceDetach(const scene::Path &path) + { + if (--m_instanceCounter.m_count == 0) { + m_entity.detach(m_keyObservers); + m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end())); + m_filter.instanceDetach(); + } + } + + EntityKeyValues &getEntity() + { + return m_entity; + } + + const EntityKeyValues &getEntity() const + { + return m_entity; + } + + scene::Traversable &getTraversable() + { + return m_model.getTraversable(); + } + + Namespaced &getNamespaced() + { + return m_nameKeys; + } + + Nameable &getNameable() + { + return m_named; + } + + TransformNode &getTransformNode() + { + return m_transform; + } + + void attach(scene::Traversable::Observer *observer) + { + m_model.attach(observer); + } + + void detach(scene::Traversable::Observer *observer) + { + m_model.detach(observer); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + if (selected) { + m_renderOrigin.render(renderer, volume, localToWorld); + } + + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume, const Matrix4 &localToWorld, bool selected) const + { + renderSolid(renderer, volume, localToWorld, selected); + if (g_showNames) { + renderer.addRenderable(m_renderName, localToWorld); + } + } + + void translate(const Vector3 &translation) + { + m_origin = origin_translated(m_origin, translation); + } + + void rotate(const Quaternion &rotation) + { + m_angles = angles_rotated(m_angles, rotation); + } + + void scale(const Vector3 &scaling) + { + m_scale = scale_scaled(m_scale, scaling); + } + + void snapto(float snap) + { + m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap); + m_originKey.write(&m_entity); + } + + void revertTransform() + { + m_origin = m_originKey.m_origin; + m_angles = m_anglesKey.m_angles; + m_scale = m_scaleKey.m_scale; + } + + void freezeTransform() + { + m_originKey.m_origin = m_origin; + m_originKey.write(&m_entity); + m_anglesKey.m_angles = m_angles; + m_anglesKey.write(&m_entity); + m_scaleKey.m_scale = m_scale; + m_scaleKey.write(&m_entity); + } + + void transformChanged() + { + revertTransform(); + m_evaluateTransform(); + updateTransform(); + } + + typedef MemberCaller TransformChangedCaller; +}; + +class PropDynamicInstance : public TargetableInstance, public TransformModifier, public Renderable { + class TypeCasts { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + m_casts = TargetableInstance::StaticTypeCasts::instance().get(); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceIdentityCast::install(m_casts); + } + + InstanceTypeCastTable &get() + { + return m_casts; + } + }; + + PropDynamic &m_contained; +public: + typedef LazyStatic StaticTypeCasts; + + STRING_CONSTANT(Name, "PropDynamicInstance"); + + PropDynamicInstance(const scene::Path &path, scene::Instance *parent, PropDynamic &miscmodel) : + TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), miscmodel.getEntity(), *this), + TransformModifier(PropDynamic::TransformChangedCaller(miscmodel), ApplyTransformCaller(*this)), + m_contained(miscmodel) + { + m_contained.instanceAttach(Instance::path()); + StaticRenderableConnectionLines::instance().attach(*this); + } + + ~PropDynamicInstance() + { + StaticRenderableConnectionLines::instance().detach(*this); + m_contained.instanceDetach(Instance::path()); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderSolid(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), getSelectable().isSelected()); + } + + void evaluateTransform() + { + if (getType() == TRANSFORM_PRIMITIVE) { + m_contained.translate(getTranslation()); + m_contained.rotate(getRotation()); + m_contained.scale(getScale()); + } + } + + void applyTransform() + { + m_contained.revertTransform(); + evaluateTransform(); + m_contained.freezeTransform(); + } + + typedef MemberCaller ApplyTransformCaller; +}; + +class PropDynamicNode : + public scene::Node::Symbiot, + public scene::Instantiable, + public scene::Cloneable, + public scene::Traversable::Observer { + 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); + } + + NodeTypeCastTable &get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + PropDynamic m_contained; + + void construct() + { + m_contained.attach(this); + } + + void destroy() + { + m_contained.detach(this); + } + +public: + typedef LazyStatic StaticTypeCasts; + + scene::Traversable &get(NullType) + { + return m_contained.getTraversable(); + } + + Snappable &get(NullType) + { + return m_contained; + } + + TransformNode &get(NullType) + { + return m_contained.getTransformNode(); + } + + Entity &get(NullType) + { + return m_contained.getEntity(); + } + + Nameable &get(NullType) + { + return m_contained.getNameable(); + } + + Namespaced &get(NullType) + { + return m_contained.getNamespaced(); + } + + PropDynamicNode(EntityClass *eclass) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + PropDynamicNode(const PropDynamicNode &other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + scene::Traversable::Observer(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), + InstanceSetEvaluateTransform::Caller(m_instances)) + { + construct(); + } + + ~PropDynamicNode() + { + destroy(); + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + scene::Node &clone() const + { + return (new PropDynamicNode(*this))->node(); + } + + void insert(scene::Node &child) + { + m_instances.insert(child); + } + + void erase(scene::Node &child) + { + m_instances.erase(child); + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new PropDynamicInstance(path, parent, m_contained); + } + + 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 &New_PropDynamic(EntityClass *eclass) +{ + return (new PropDynamicNode(eclass))->node(); +} + +void PropDynamic_construct() +{ +} + +void PropDynamic_destroy() +{ +} diff --git a/plugins/entity/prop_dynamic.h b/plugins/entity/prop_dynamic.h new file mode 100644 index 0000000..56d30f5 --- /dev/null +++ b/plugins/entity/prop_dynamic.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_PROPDYNAMIC_H ) +#define INCLUDED_PROPDYNAMIC_H + +scene::Node &New_PropDynamic(EntityClass *eclass); + +void PropDynamic_construct(); + +void PropDynamic_destroy(); + +#endif diff --git a/plugins/entity/rotation.h b/plugins/entity/rotation.h new file mode 100644 index 0000000..c11bb98 --- /dev/null +++ b/plugins/entity/rotation.h @@ -0,0 +1,192 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_ROTATION_H ) +#define INCLUDED_ROTATION_H + +#include "ientity.h" + +#include "stream/stringstream.h" +#include "math/quaternion.h" +#include "generic/callback.h" +#include "stringio.h" + +#include "angle.h" + +typedef float Float9[9]; + +inline void default_rotation(Float9 rotation) +{ + rotation[0] = 1; + rotation[1] = 0; + rotation[2] = 0; + rotation[3] = 0; + rotation[4] = 1; + rotation[5] = 0; + rotation[6] = 0; + rotation[7] = 0; + rotation[8] = 1; +} + +inline void write_rotation(const Float9 rotation, Entity *entity, const char *key = "rotation") +{ + if (rotation[0] == 1 + && rotation[1] == 0 + && rotation[2] == 0 + && rotation[3] == 0 + && rotation[4] == 1 + && rotation[5] == 0 + && rotation[6] == 0 + && rotation[7] == 0 + && rotation[8] == 1) { + entity->setKeyValue(key, ""); + } else { + StringOutputStream value(256); + value << rotation[0] << ' ' + << rotation[1] << ' ' + << rotation[2] << ' ' + << rotation[3] << ' ' + << rotation[4] << ' ' + << rotation[5] << ' ' + << rotation[6] << ' ' + << rotation[7] << ' ' + << rotation[8]; + entity->setKeyValue(key, value.c_str()); + } +} + +inline void read_rotation(Float9 rotation, const char *value) +{ + if (!string_parse_vector(value, rotation, rotation + 9)) { + default_rotation(rotation); + } +} + +inline Matrix4 rotation_toMatrix(const Float9 rotation) +{ + return Matrix4( + rotation[0], + rotation[1], + rotation[2], + 0, + rotation[3], + rotation[4], + rotation[5], + 0, + rotation[6], + rotation[7], + rotation[8], + 0, + 0, + 0, + 0, + 1 + ); +} + +inline void rotation_fromMatrix(Float9 rotation, const Matrix4 &matrix) +{ + rotation[0] = matrix.xx(); + rotation[1] = matrix.xy(); + rotation[2] = matrix.xz(); + rotation[3] = matrix.yx(); + rotation[4] = matrix.yy(); + rotation[5] = matrix.yz(); + rotation[6] = matrix.zx(); + rotation[7] = matrix.zy(); + rotation[8] = matrix.zz(); +} + +inline void rotation_assign(Float9 rotation, const Float9 other) +{ + rotation[0] = other[0]; + rotation[1] = other[1]; + rotation[2] = other[2]; + rotation[3] = other[3]; + rotation[4] = other[4]; + rotation[5] = other[5]; + rotation[6] = other[6]; + rotation[7] = other[7]; + rotation[8] = other[8]; +} + +inline void rotation_rotate(Float9 rotation, const Quaternion &rotate) +{ + rotation_fromMatrix(rotation, + matrix4_multiplied_by_matrix4( + rotation_toMatrix(rotation), + matrix4_rotation_for_quaternion_quantised(rotate) + ) + ); +} + +inline void read_angle(Float9 rotation, const char *value) +{ + float angle; + if (!string_parse_float(value, angle)) { + default_rotation(rotation); + } else { + rotation_fromMatrix(rotation, matrix4_rotation_for_z_degrees(angle)); + } +} + +class RotationKey { + Callback m_rotationChanged; +public: + Float9 m_rotation; + + + RotationKey(const Callback &rotationChanged) + : m_rotationChanged(rotationChanged) + { + default_rotation(m_rotation); + } + + void angleChanged(const char *value) + { + read_angle(m_rotation, value); + m_rotationChanged(); + } + + typedef MemberCaller AngleChangedCaller; + + void rotationChanged(const char *value) + { + read_rotation(m_rotation, value); + m_rotationChanged(); + } + + typedef MemberCaller RotationChangedCaller; + + void write(Entity *entity) const + { + Vector3 euler = matrix4_get_rotation_euler_xyz_degrees(rotation_toMatrix(m_rotation)); + if (euler[0] == 0 && euler[1] == 0) { + entity->setKeyValue("rotation", ""); + write_angle(euler[2], entity); + } else { + entity->setKeyValue("angle", ""); + write_rotation(m_rotation, entity); + } + } +}; + +#endif diff --git a/plugins/entity/scale.h b/plugins/entity/scale.h new file mode 100644 index 0000000..a6115bd --- /dev/null +++ b/plugins/entity/scale.h @@ -0,0 +1,124 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_SCALE_H ) +#define INCLUDED_SCALE_H + +#include "ientity.h" + +#include "math/matrix.h" +#include "generic/callback.h" +#include "stringio.h" + +const Vector3 SCALEKEY_IDENTITY = Vector3(1, 1, 1); + +inline void default_scale(Vector3 &scale) +{ + scale = SCALEKEY_IDENTITY; +} + +inline void read_scale(Vector3 &scalevec, const char *value) +{ + float scale; + if (!string_parse_float(value, scale) + || scale == 0) { + default_scale(scalevec); + } else { + scalevec = Vector3(scale, scale, scale); + } +} + +inline void read_scalevec(Vector3 &scale, const char *value) +{ + if (!string_parse_vector3(value, scale) + || scale[0] == 0 + || scale[1] == 0 + || scale[2] == 0) { + default_scale(scale); + } +} + +inline void write_scale(const Vector3 &scale, Entity *entity) +{ + if (scale[0] == 1 && scale[1] == 1 && scale[2] == 1) { + entity->setKeyValue("modelscale", ""); + entity->setKeyValue("modelscale_vec", ""); + } else { + char value[64]; + + if (scale[0] == scale[1] && scale[0] == scale[2]) { + sprintf(value, "%f", scale[0]); + entity->setKeyValue("modelscale_vec", ""); + entity->setKeyValue("modelscale", value); + } else { + sprintf(value, "%f %f %f", scale[0], scale[1], scale[2]); + entity->setKeyValue("modelscale", ""); + entity->setKeyValue("modelscale_vec", value); + } + } +} + +inline Vector3 scale_scaled(const Vector3 &scale, const Vector3 &scaling) +{ + return matrix4_get_scale_vec3( + matrix4_multiplied_by_matrix4( + matrix4_scale_for_vec3(scale), + matrix4_scale_for_vec3(scaling) + ) + ); +} + + +class ScaleKey { + Callback m_scaleChanged; +public: + Vector3 m_scale; + + + ScaleKey(const Callback &scaleChanged) + : m_scaleChanged(scaleChanged), m_scale(SCALEKEY_IDENTITY) + { + } + + void uniformScaleChanged(const char *value) + { + read_scale(m_scale, value); + m_scaleChanged(); + } + + typedef MemberCaller UniformScaleChangedCaller; + + void scaleChanged(const char *value) + { + read_scalevec(m_scale, value); + m_scaleChanged(); + } + + typedef MemberCaller ScaleChangedCaller; + + void write(Entity *entity) const + { + write_scale(m_scale, entity); + } +}; + + +#endif diff --git a/plugins/entity/skincache.cpp b/plugins/entity/skincache.cpp new file mode 100644 index 0000000..5486aad --- /dev/null +++ b/plugins/entity/skincache.cpp @@ -0,0 +1,335 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "skincache.h" + +#include "ifilesystem.h" +#include "iscriplib.h" +#include "iarchive.h" +#include "modelskin.h" + +#include + +#include "stream/stringstream.h" +#include "generic/callback.h" +#include "container/cache.h" +#include "container/hashfunc.h" +#include "os/path.h" +#include "moduleobservers.h" +#include "modulesystem/singletonmodule.h" +#include "stringio.h" + +void parseShaderName(CopiedString &name, const char *token) +{ + StringOutputStream cleaned(256); + cleaned << PathCleaned(token); + name = cleaned.c_str(); +} + +class Doom3ModelSkin { + typedef std::map Remaps; + Remaps m_remaps; +public: + bool parseTokens(Tokeniser &tokeniser) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "{")); + tokeniser.nextLine(); + for (;;) { + const char *token = tokeniser.getToken(); + if (token == 0) { + return false; + } + if (string_equal(token, "}")) { + tokeniser.nextLine(); + return true; + } else if (string_equal(token, "model")) { + //const char* model = + tokeniser.getToken(); + } else { + CopiedString from, to; + parseShaderName(from, token); + + tokeniser.nextLine(); // hack to handle badly formed skins + + parseShaderName(to, tokeniser.getToken()); + + if (!string_equal(from.c_str(), to.c_str())) { + m_remaps.insert(Remaps::value_type(from, to)); + } + } + tokeniser.nextLine(); + } + } + + const char *getRemap(const char *name) const + { + Remaps::const_iterator i = m_remaps.find(name); + if (i != m_remaps.end()) { + return (*i).second.c_str(); + } + return ""; + } + + void forEachRemap(const SkinRemapCallback &callback) const + { + for (Remaps::const_iterator i = m_remaps.begin(); i != m_remaps.end(); ++i) { + callback(SkinRemap((*i).first.c_str(), (*i).second.c_str())); + } + } +}; + +class GlobalSkins { +public: + typedef std::map SkinMap; + SkinMap m_skins; + Doom3ModelSkin g_nullSkin; + + Doom3ModelSkin &getSkin(const char *name) + { + SkinMap::iterator i = m_skins.find(name); + if (i != m_skins.end()) { + return (*i).second; + } + + return g_nullSkin; + } + + bool parseTokens(Tokeniser &tokeniser) + { + tokeniser.nextLine(); + for (;;) { + const char *token = tokeniser.getToken(); + if (token == 0) { + // end of token stream + return true; + } + if (!string_equal(token, "skin")) { + Tokeniser_unexpectedError(tokeniser, token, "skin"); + return false; + } + const char *other = tokeniser.getToken(); + if (other == 0) { + Tokeniser_unexpectedError(tokeniser, token, "#string"); + return false; + } + CopiedString name; + parseShaderName(name, other); + Doom3ModelSkin &skin = m_skins[name]; + RETURN_FALSE_IF_FAIL(skin.parseTokens(tokeniser)); + } + } + + void parseFile(const char *name) + { + StringOutputStream relativeName(64); + relativeName << "skins/" << name; + ArchiveTextFile *file = GlobalFileSystem().openTextFile(relativeName.c_str()); + if (file != 0) { + globalOutputStream() << "parsing skins from " << makeQuoted(name) << "\n"; + { + Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(file->getInputStream()); + parseTokens(tokeniser); + tokeniser.release(); + } + file->release(); + } else { + globalErrorStream() << "failed to open " << makeQuoted(name) << "\n"; + } + } + + typedef MemberCaller ParseFileCaller; + + void construct() + { + GlobalFileSystem().forEachFile("skins/", "skin", ParseFileCaller(*this)); + } + + void destroy() + { + m_skins.clear(); + } + + void realise() + { + construct(); + } + + void unrealise() + { + destroy(); + } +}; + +GlobalSkins g_skins; + + +class Doom3ModelSkinCacheElement : public ModelSkin { + ModuleObservers m_observers; + Doom3ModelSkin *m_skin; +public: + Doom3ModelSkinCacheElement() : m_skin(0) + { + } + + void attach(ModuleObserver &observer) + { + m_observers.attach(observer); + if (realised()) { + observer.realise(); + } + } + + void detach(ModuleObserver &observer) + { + if (realised()) { + observer.unrealise(); + } + m_observers.detach(observer); + } + + bool realised() const + { + return m_skin != 0; + } + + void realise(const char *name) + { + ASSERT_MESSAGE(!realised(), "Doom3ModelSkinCacheElement::realise: already realised"); + m_skin = &g_skins.getSkin(name); + m_observers.realise(); + } + + void unrealise() + { + ASSERT_MESSAGE(realised(), "Doom3ModelSkinCacheElement::unrealise: not realised"); + m_observers.unrealise(); + m_skin = 0; + } + + const char *getRemap(const char *name) const + { + ASSERT_MESSAGE(realised(), "Doom3ModelSkinCacheElement::getRemap: not realised"); + return m_skin->getRemap(name); + } + + void forEachRemap(const SkinRemapCallback &callback) const + { + ASSERT_MESSAGE(realised(), "Doom3ModelSkinCacheElement::forEachRemap: not realised"); + m_skin->forEachRemap(callback); + } +}; + +class Doom3ModelSkinCache : public ModelSkinCache, public ModuleObserver { + class CreateDoom3ModelSkin { + Doom3ModelSkinCache &m_cache; + public: + explicit CreateDoom3ModelSkin(Doom3ModelSkinCache &cache) + : m_cache(cache) + { + } + + Doom3ModelSkinCacheElement *construct(const CopiedString &name) + { + Doom3ModelSkinCacheElement *skin = new Doom3ModelSkinCacheElement; + if (m_cache.realised()) { + skin->realise(name.c_str()); + } + return skin; + } + + void destroy(Doom3ModelSkinCacheElement *skin) + { + if (m_cache.realised()) { + skin->unrealise(); + } + delete skin; + } + }; + + typedef HashedCache, CreateDoom3ModelSkin> Cache; + Cache m_cache; + bool m_realised; + +public: + typedef ModelSkinCache Type; + + STRING_CONSTANT(Name, "*"); + + ModelSkinCache *getTable() + { + return this; + } + + Doom3ModelSkinCache() : m_cache(CreateDoom3ModelSkin(*this)), m_realised(false) + { + GlobalFileSystem().attach(*this); + } + + ~Doom3ModelSkinCache() + { + GlobalFileSystem().detach(*this); + } + + ModelSkin &capture(const char *name) + { + return *m_cache.capture(name); + } + + void release(const char *name) + { + m_cache.release(name); + } + + bool realised() const + { + return m_realised; + } + + void realise() + { + g_skins.realise(); + m_realised = true; + for (Cache::iterator i = m_cache.begin(); i != m_cache.end(); ++i) { + (*i).value->realise((*i).key.c_str()); + } + } + + void unrealise() + { + m_realised = false; + for (Cache::iterator i = m_cache.begin(); i != m_cache.end(); ++i) { + (*i).value->unrealise(); + } + g_skins.unrealise(); + } +}; + +class Doom3ModelSkinCacheDependencies : public GlobalFileSystemModuleRef, public GlobalScripLibModuleRef { +}; + +typedef SingletonModule Doom3ModelSkinCacheModule; + +Doom3ModelSkinCacheModule g_Doom3ModelSkinCacheModule; + +void Doom3ModelSkinCacheModule_selfRegister(ModuleServer &server) +{ + g_Doom3ModelSkinCacheModule.selfRegister(); +} diff --git a/plugins/entity/skincache.h b/plugins/entity/skincache.h new file mode 100644 index 0000000..7970843 --- /dev/null +++ b/plugins/entity/skincache.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_SKINCACHE_H ) +#define INCLUDED_SKINCACHE_H + +class ModuleServer; + +void Doom3ModelSkinCacheModule_selfRegister(ModuleServer &server); + +#endif diff --git a/plugins/entity/targetable.cpp b/plugins/entity/targetable.cpp new file mode 100644 index 0000000..a20b291 --- /dev/null +++ b/plugins/entity/targetable.cpp @@ -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 + */ + +#include "targetable.h" + +typedef std::map targetnames_t; + +const char *g_targetable_nameKey = "targetname"; + +targetnames_t g_targetnames; + +targetables_t *getTargetables(const char *targetname) +{ + if (targetname[0] == '\0') { + return 0; + } + return &g_targetnames[targetname]; +} + +Shader *RenderableTargetingEntity::m_state; diff --git a/plugins/entity/targetable.h b/plugins/entity/targetable.h new file mode 100644 index 0000000..dc0270f --- /dev/null +++ b/plugins/entity/targetable.h @@ -0,0 +1,451 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_TARGETABLE_H ) +#define INCLUDED_TARGETABLE_H + +#include +#include + +#include "cullable.h" +#include "renderable.h" + +#include "math/line.h" +#include "render.h" +#include "generic/callback.h" +#include "selectionlib.h" +#include "entitylib.h" +#include "eclasslib.h" +#include "stringio.h" + +class Targetable { +public: + virtual const Vector3 &world_position() const = 0; +}; + +typedef std::set targetables_t; + +extern const char *g_targetable_nameKey; + +targetables_t *getTargetables(const char *targetname); + +class EntityConnectionLine : public OpenGLRenderable { +public: + Vector3 start; + Vector3 end; + + void render(RenderStateFlags state) const + { + float s1[2], s2[2]; + Vector3 dir(vector3_subtracted(end, start)); + double len = vector3_length(dir); + vector3_scale(dir, 8.0 * (1.0 / len)); + s1[0] = dir[0] - dir[1]; + s1[1] = dir[0] + dir[1]; + s2[0] = dir[0] + dir[1]; + s2[1] = -dir[0] + dir[1]; + + glBegin(GL_LINES); + + glVertex3fv(vector3_to_array(start)); + glVertex3fv(vector3_to_array(end)); + + len *= 0.0625; // half / 8 + + Vector3 arrow(start); + for (unsigned int i = 0, count = (len < 32) ? 1 : static_cast( len * 0.0625 ); i < count; i++) { + vector3_add(arrow, vector3_scaled(dir, (len < 32) ? len : 32)); + glVertex3fv(vector3_to_array(arrow)); + glVertex3f(arrow[0] + s1[0], arrow[1] + s1[1], arrow[2] + dir[2]); + glVertex3fv(vector3_to_array(arrow)); + glVertex3f(arrow[0] + s2[0], arrow[1] + s2[1], arrow[2] + dir[2]); + } + + glEnd(); + } +}; + +class TargetedEntity { + Targetable &m_targetable; + targetables_t *m_targets; + + void construct() + { + if (m_targets != 0) { + m_targets->insert(&m_targetable); + } + } + + void destroy() + { + if (m_targets != 0) { + m_targets->erase(&m_targetable); + } + } + +public: + TargetedEntity(Targetable &targetable) + : m_targetable(targetable), m_targets(getTargetables("")) + { + construct(); + } + + ~TargetedEntity() + { + destroy(); + } + + void targetnameChanged(const char *name) + { + destroy(); + m_targets = getTargetables(name); + construct(); + } + + typedef MemberCaller TargetnameChangedCaller; +}; + + +class TargetingEntity { + targetables_t *m_targets; +public: + TargetingEntity() : + m_targets(getTargetables("")) + { + } + + void targetChanged(const char *target) + { + m_targets = getTargetables(target); + } + + typedef MemberCaller TargetChangedCaller; + + typedef targetables_t::iterator iterator; + + iterator begin() const + { + if (m_targets == 0) { + return iterator(); + } + return m_targets->begin(); + } + + iterator end() const + { + if (m_targets == 0) { + return iterator(); + } + return m_targets->end(); + } + + size_t size() const + { + if (m_targets == 0) { + return 0; + } + return m_targets->size(); + } + + bool empty() const + { + return m_targets == 0 || m_targets->empty(); + } +}; + + +template +void TargetingEntity_forEach(const TargetingEntity &targets, const Functor &functor) +{ + for (TargetingEntity::iterator i = targets.begin(); i != targets.end(); ++i) { + functor((*i)->world_position()); + } +} + +typedef std::map TargetingEntities; + +template +void TargetingEntities_forEach(const TargetingEntities &targetingEntities, const Functor &functor) +{ + for (TargetingEntities::const_iterator i = targetingEntities.begin(); i != targetingEntities.end(); ++i) { + TargetingEntity_forEach((*i).second, functor); + } +} + +class TargetLinesPushBack { + RenderablePointVector &m_targetLines; + const Vector3 &m_worldPosition; + const VolumeTest &m_volume; +public: + TargetLinesPushBack(RenderablePointVector &targetLines, const Vector3 &worldPosition, const VolumeTest &volume) : + m_targetLines(targetLines), m_worldPosition(worldPosition), m_volume(volume) + { + } + + void operator()(const Vector3 &worldPosition) const + { + if (m_volume.TestLine(segment_for_startend(m_worldPosition, worldPosition))) { + m_targetLines.push_back(PointVertex(reinterpret_cast( m_worldPosition ))); + m_targetLines.push_back(PointVertex(reinterpret_cast( worldPosition ))); + } + } +}; + +class TargetKeys : public Entity::Observer { + TargetingEntities m_targetingEntities; + Callback m_targetsChanged; + + bool readTargetKey(const char *key, std::size_t &index) + { + if (string_equal_n(key, "target", 6)) { + index = 0; + if (string_empty(key + 6) || string_parse_size(key + 6, index)) { + return true; + } + } + if (string_equal(key, "killtarget")) { + index = -1; + return true; + } + return false; + } + +public: + void setTargetsChanged(const Callback &targetsChanged) + { + m_targetsChanged = targetsChanged; + } + + void targetsChanged() + { + m_targetsChanged(); + } + + void insert(const char *key, EntityKeyValue &value) + { + std::size_t index; + if (readTargetKey(key, index)) { + TargetingEntities::iterator i = m_targetingEntities.insert( + TargetingEntities::value_type(index, TargetingEntity())).first; + value.attach(TargetingEntity::TargetChangedCaller((*i).second)); + targetsChanged(); + } + } + + void erase(const char *key, EntityKeyValue &value) + { + std::size_t index; + if (readTargetKey(key, index)) { + TargetingEntities::iterator i = m_targetingEntities.find(index); + value.detach(TargetingEntity::TargetChangedCaller((*i).second)); + m_targetingEntities.erase(i); + targetsChanged(); + } + } + + const TargetingEntities &get() const + { + return m_targetingEntities; + } +}; + + +class RenderableTargetingEntity { + TargetingEntity &m_targets; + mutable RenderablePointVector m_target_lines; +public: + static Shader *m_state; + + RenderableTargetingEntity(TargetingEntity &targets) + : m_targets(targets), m_target_lines(GL_LINES) + { + } + + void compile(const VolumeTest &volume, const Vector3 &world_position) const + { + m_target_lines.clear(); + m_target_lines.reserve(m_targets.size() * 2); + TargetingEntity_forEach(m_targets, TargetLinesPushBack(m_target_lines, world_position, volume)); + } + + void render(Renderer &renderer, const VolumeTest &volume, const Vector3 &world_position) const + { + if (!m_targets.empty()) { + compile(volume, world_position); + if (!m_target_lines.empty()) { + renderer.addRenderable(m_target_lines, g_matrix4_identity); + } + } + } +}; + +class RenderableTargetingEntities { + const TargetingEntities &m_targets; + mutable RenderablePointVector m_target_lines; +public: + static Shader *m_state; + + RenderableTargetingEntities(const TargetingEntities &targets) + : m_targets(targets), m_target_lines(GL_LINES) + { + } + + void compile(const VolumeTest &volume, const Vector3 &world_position) const + { + m_target_lines.clear(); + TargetingEntities_forEach(m_targets, TargetLinesPushBack(m_target_lines, world_position, volume)); + } + + void render(Renderer &renderer, const VolumeTest &volume, const Vector3 &world_position) const + { + if (!m_targets.empty()) { + compile(volume, world_position); + if (!m_target_lines.empty()) { + renderer.addRenderable(m_target_lines, g_matrix4_identity); + } + } + } +}; + + +class TargetableInstance : + public SelectableInstance, + public Targetable, + public Entity::Observer { + mutable Vertex3f m_position; + EntityKeyValues &m_entity; + TargetKeys m_targeting; + TargetedEntity m_targeted; + RenderableTargetingEntities m_renderable; +public: + + TargetableInstance( + const scene::Path &path, + scene::Instance *parent, + void *instance, + InstanceTypeCastTable &casts, + EntityKeyValues &entity, + Targetable &targetable + ) : + SelectableInstance(path, parent, instance, casts), + m_entity(entity), + m_targeted(targetable), + m_renderable(m_targeting.get()) + { + m_entity.attach(*this); + m_entity.attach(m_targeting); + } + + ~TargetableInstance() + { + m_entity.detach(m_targeting); + m_entity.detach(*this); + } + + void setTargetsChanged(const Callback &targetsChanged) + { + m_targeting.setTargetsChanged(targetsChanged); + } + + void targetsChanged() + { + m_targeting.targetsChanged(); + } + + void insert(const char *key, EntityKeyValue &value) + { + if (string_equal(key, g_targetable_nameKey)) { + value.attach(TargetedEntity::TargetnameChangedCaller(m_targeted)); + } + } + + void erase(const char *key, EntityKeyValue &value) + { + if (string_equal(key, g_targetable_nameKey)) { + value.detach(TargetedEntity::TargetnameChangedCaller(m_targeted)); + } + } + + const Vector3 &world_position() const + { +#if 1 + const AABB &bounds = Instance::worldAABB(); + if (aabb_valid(bounds)) { + return bounds.origin; + } +#else + const AABB& childBounds = Instance::childBounds(); + if ( aabb_valid( childBounds ) ) { + return childBounds.origin; + } +#endif + return vector4_to_vector3(localToWorld().t()); + } + + void render(Renderer &renderer, const VolumeTest &volume) const + { + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly); + renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials); + m_renderable.render(renderer, volume, world_position()); + } + + const TargetingEntities &getTargeting() const + { + return m_targeting.get(); + } +}; + + +class RenderableConnectionLines : public Renderable { + typedef std::set TargetableInstances; + TargetableInstances m_instances; +public: + void attach(TargetableInstance &instance) + { + ASSERT_MESSAGE(m_instances.find(&instance) == m_instances.end(), "cannot attach instance"); + m_instances.insert(&instance); + } + + void detach(TargetableInstance &instance) + { + ASSERT_MESSAGE(m_instances.find(&instance) != m_instances.end(), "cannot detach instance"); + m_instances.erase(&instance); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + for (TargetableInstances::const_iterator i = m_instances.begin(); i != m_instances.end(); ++i) { + if ((*i)->path().top().get().visible()) { + (*i)->render(renderer, volume); + } + } + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + renderSolid(renderer, volume); + } +}; + +typedef Static StaticRenderableConnectionLines; + +#endif diff --git a/plugins/image/CMakeLists.txt b/plugins/image/CMakeLists.txt new file mode 100644 index 0000000..e1f40cc --- /dev/null +++ b/plugins/image/CMakeLists.txt @@ -0,0 +1,13 @@ +radiant_plugin(image + bmp.cpp bmp.h + dds.cpp dds.h + image.cpp + jpeg.cpp jpeg.h + ktx.cpp ktx.h + pcx.cpp pcx.h + tga.cpp tga.h + ) + +find_package(JPEG REQUIRED) +target_include_directories(image PRIVATE ${JPEG_INCLUDE_DIR}) +target_link_libraries(image PRIVATE ddslib etclib ${JPEG_LIBRARIES}) diff --git a/plugins/image/bmp.cpp b/plugins/image/bmp.cpp new file mode 100644 index 0000000..4b27227 --- /dev/null +++ b/plugins/image/bmp.cpp @@ -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 + */ + +#include "bmp.h" + +#include "ifilesystem.h" + +typedef unsigned char byte; + +#include "imagelib.h" +#include "bytestreamutils.h" + + +typedef unsigned char PaletteEntry[4]; +typedef struct { + char id[2]; + unsigned long fileSize; + unsigned long reserved0; + unsigned long bitmapDataOffset; + unsigned long bitmapHeaderSize; + unsigned long width; + unsigned long height; + unsigned short planes; + unsigned short bitsPerPixel; + unsigned long compression; + unsigned long bitmapDataSize; + unsigned long hRes; + unsigned long vRes; + unsigned long colors; + unsigned long importantColors; + PaletteEntry palette[256]; +} BMPHeader_t; + +class ReadPixel8 { + PaletteEntry *m_palette; +public: + ReadPixel8(PaletteEntry *palette) : m_palette(palette) + { + } + + void operator()(PointerInputStream &inputStream, byte *&pixbuf) const + { + byte palIndex; + inputStream.read(&palIndex, 1); + *pixbuf++ = m_palette[palIndex][2]; + *pixbuf++ = m_palette[palIndex][1]; + *pixbuf++ = m_palette[palIndex][0]; + *pixbuf++ = 0xff; + } +}; + +class ReadPixel16 { +public: + void operator()(PointerInputStream &inputStream, byte *&pixbuf) const + { + unsigned short shortPixel; + inputStream.read(reinterpret_cast( &shortPixel ), sizeof(unsigned short)); //!\todo Is this endian safe? + *pixbuf++ = static_cast( shortPixel & (31 << 10)) >> 7; + *pixbuf++ = static_cast( shortPixel & (31 << 5)) >> 2; + *pixbuf++ = static_cast( shortPixel & (31)) << 3; + *pixbuf++ = 0xff; + } +}; + +class ReadPixel24 { +public: + void operator()(PointerInputStream &inputStream, byte *&pixbuf) const + { + byte bgr[3]; + inputStream.read(bgr, 3); + *pixbuf++ = bgr[2]; + *pixbuf++ = bgr[1]; + *pixbuf++ = bgr[0]; + *pixbuf++ = 255; + } +}; + +class ReadPixel32 { +public: + void operator()(PointerInputStream &inputStream, byte *&pixbuf) const + { + byte bgra[4]; + inputStream.read(bgra, 4); + *pixbuf++ = bgra[2]; + *pixbuf++ = bgra[1]; + *pixbuf++ = bgra[0]; + *pixbuf++ = bgra[3]; + } +}; + +template +void ReadBMP(PointerInputStream &inputStream, byte *bmpRGBA, int rows, int columns, ReadPixel readPixel) +{ + for (int row = rows - 1; row >= 0; row--) { + byte *pixbuf = bmpRGBA + row * columns * 4; + + for (int column = 0; column < columns; column++) { + readPixel(inputStream, pixbuf); + } + } +} + +Image *LoadBMPBuff(PointerInputStream &inputStream, std::size_t length) +{ + BMPHeader_t bmpHeader; + inputStream.read(reinterpret_cast( bmpHeader.id ), 2); + bmpHeader.fileSize = istream_read_uint32_le(inputStream); + bmpHeader.reserved0 = istream_read_uint32_le(inputStream); + bmpHeader.bitmapDataOffset = istream_read_uint32_le(inputStream); + bmpHeader.bitmapHeaderSize = istream_read_uint32_le(inputStream); + bmpHeader.width = istream_read_uint32_le(inputStream); + bmpHeader.height = istream_read_uint32_le(inputStream); + bmpHeader.planes = istream_read_uint16_le(inputStream); + bmpHeader.bitsPerPixel = istream_read_uint16_le(inputStream); + bmpHeader.compression = istream_read_uint32_le(inputStream); + bmpHeader.bitmapDataSize = istream_read_uint32_le(inputStream); + bmpHeader.hRes = istream_read_uint32_le(inputStream); + bmpHeader.vRes = istream_read_uint32_le(inputStream); + bmpHeader.colors = istream_read_uint32_le(inputStream); + bmpHeader.importantColors = istream_read_uint32_le(inputStream); + + if (bmpHeader.bitsPerPixel == 8) { + int paletteSize = bmpHeader.colors * 4; + inputStream.read(reinterpret_cast( bmpHeader.palette ), paletteSize); + } + + if (bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M') { + globalErrorStream() << "LoadBMP: only Windows-style BMP files supported\n"; + return 0; + } + if (bmpHeader.fileSize != length) { + globalErrorStream() << "LoadBMP: header size does not match file size (" << Unsigned(bmpHeader.fileSize) + << " vs. " << Unsigned(length) << ")\n"; + return 0; + } + if (bmpHeader.compression != 0) { + globalErrorStream() << "LoadBMP: only uncompressed BMP files supported\n"; + return 0; + } + if (bmpHeader.bitsPerPixel < 8) { + globalErrorStream() << "LoadBMP: monochrome and 4-bit BMP files not supported\n"; + return 0; + } + + int columns = bmpHeader.width; + int rows = bmpHeader.height; + if (rows < 0) { + rows = -rows; + } + + RGBAImage *image = new RGBAImage(columns, rows); + + switch (bmpHeader.bitsPerPixel) { + case 8: + ReadBMP(inputStream, image->getRGBAPixels(), rows, columns, ReadPixel8(bmpHeader.palette)); + break; + case 16: + ReadBMP(inputStream, image->getRGBAPixels(), rows, columns, ReadPixel16()); + break; + case 24: + ReadBMP(inputStream, image->getRGBAPixels(), rows, columns, ReadPixel24()); + break; + case 32: + ReadBMP(inputStream, image->getRGBAPixels(), rows, columns, ReadPixel32()); + break; + default: + globalErrorStream() << "LoadBMP: illegal pixel_size '" << bmpHeader.bitsPerPixel << "'\n"; + image->release(); + return 0; + } + return image; +} + +Image *LoadBMP(ArchiveFile &file) +{ + ScopedArchiveBuffer buffer(file); + PointerInputStream inputStream(buffer.buffer); + return LoadBMPBuff(inputStream, buffer.length); +} diff --git a/plugins/image/bmp.h b/plugins/image/bmp.h new file mode 100644 index 0000000..043c289 --- /dev/null +++ b/plugins/image/bmp.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_BMP_H ) +#define INCLUDED_BMP_H + +class Image; + +class ArchiveFile; + +Image *LoadBMP(ArchiveFile &file); + +#endif diff --git a/plugins/image/dds.cpp b/plugins/image/dds.cpp new file mode 100644 index 0000000..79941e8 --- /dev/null +++ b/plugins/image/dds.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 "dds.h" + +#include + +#include "ifilesystem.h" +#include "iarchive.h" +#include "idatastream.h" + +#include "ddslib.h" +#include "imagelib.h" + +Image *LoadDDSBuff(const byte *buffer) +{ + int width, height; + ddsPF_t pixelFormat; + if (DDSGetInfo(reinterpret_cast( const_cast( buffer )), &width, &height, &pixelFormat) == + -1) { + return 0; + } + + RGBAImage *image = new RGBAImage(width, height); + + if (DDSDecompress(reinterpret_cast( const_cast( buffer )), image->getRGBAPixels()) == -1) { + image->release(); + return 0; + } + return image; +} + +Image *LoadDDS(ArchiveFile &file) +{ + ScopedArchiveBuffer buffer(file); + return LoadDDSBuff(buffer.buffer); +} diff --git a/plugins/image/dds.h b/plugins/image/dds.h new file mode 100644 index 0000000..5e65831 --- /dev/null +++ b/plugins/image/dds.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_DDS_H ) +#define INCLUDED_DDS_H + +class Image; + +class ArchiveFile; + +Image *LoadDDS(ArchiveFile &file); + +#endif diff --git a/plugins/image/image.cpp b/plugins/image/image.cpp new file mode 100644 index 0000000..f7f7352 --- /dev/null +++ b/plugins/image/image.cpp @@ -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 + */ + +#include "ifilesystem.h" +#include "iimage.h" + +#include "jpeg.h" +#include "tga.h" +#include "bmp.h" +#include "pcx.h" +#include "dds.h" +#include "ktx.h" + + +#include "modulesystem/singletonmodule.h" + +class ImageDependencies : public GlobalFileSystemModuleRef { +}; + +class ImageTGAAPI { + _QERPlugImageTable m_imagetga; +public: + typedef _QERPlugImageTable Type; + + STRING_CONSTANT(Name, "tga"); + + ImageTGAAPI() + { + m_imagetga.loadImage = LoadTGA; + } + + _QERPlugImageTable *getTable() + { + return &m_imagetga; + } +}; + +typedef SingletonModule ImageTGAModule; + +ImageTGAModule g_ImageTGAModule; + + +class ImageJPGAPI { + _QERPlugImageTable m_imagejpg; +public: + typedef _QERPlugImageTable Type; + + STRING_CONSTANT(Name, "jpg"); + + ImageJPGAPI() + { + m_imagejpg.loadImage = LoadJPG; + } + + _QERPlugImageTable *getTable() + { + return &m_imagejpg; + } +}; + +typedef SingletonModule ImageJPGModule; + +ImageJPGModule g_ImageJPGModule; + + +class ImageBMPAPI { + _QERPlugImageTable m_imagebmp; +public: + typedef _QERPlugImageTable Type; + + STRING_CONSTANT(Name, "bmp"); + + ImageBMPAPI() + { + m_imagebmp.loadImage = LoadBMP; + } + + _QERPlugImageTable *getTable() + { + return &m_imagebmp; + } +}; + +typedef SingletonModule ImageBMPModule; + +ImageBMPModule g_ImageBMPModule; + + +class ImagePCXAPI { + _QERPlugImageTable m_imagepcx; +public: + typedef _QERPlugImageTable Type; + + STRING_CONSTANT(Name, "pcx"); + + ImagePCXAPI() + { + m_imagepcx.loadImage = LoadPCX32; + } + + _QERPlugImageTable *getTable() + { + return &m_imagepcx; + } +}; + +typedef SingletonModule ImagePCXModule; + +ImagePCXModule g_ImagePCXModule; + + +class ImageDDSAPI { + _QERPlugImageTable m_imagedds; +public: + typedef _QERPlugImageTable Type; + + STRING_CONSTANT(Name, "dds"); + + ImageDDSAPI() + { + m_imagedds.loadImage = LoadDDS; + } + + _QERPlugImageTable *getTable() + { + return &m_imagedds; + } +}; + +typedef SingletonModule ImageDDSModule; + +ImageDDSModule g_ImageDDSModule; + + +class ImageKTXAPI { + _QERPlugImageTable m_imagektx; +public: + typedef _QERPlugImageTable Type; + + STRING_CONSTANT(Name, "ktx"); + + ImageKTXAPI() + { + m_imagektx.loadImage = LoadKTX; + } + + _QERPlugImageTable *getTable() + { + return &m_imagektx; + } +}; + +typedef SingletonModule ImageKTXModule; + +ImageKTXModule g_ImageKTXModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer &server) +{ + initialiseModule(server); + + g_ImageTGAModule.selfRegister(); + g_ImageJPGModule.selfRegister(); + g_ImageBMPModule.selfRegister(); + g_ImagePCXModule.selfRegister(); + g_ImageDDSModule.selfRegister(); + g_ImageKTXModule.selfRegister(); +} diff --git a/plugins/image/imageq3.def b/plugins/image/imageq3.def new file mode 100644 index 0000000..e8c608f --- /dev/null +++ b/plugins/image/imageq3.def @@ -0,0 +1,7 @@ +; imageq3.def : Declares the module parameters for the DLL. + +LIBRARY "ImageQ3" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/image/jpeg.cpp b/plugins/image/jpeg.cpp new file mode 100644 index 0000000..fdc1ef0 --- /dev/null +++ b/plugins/image/jpeg.cpp @@ -0,0 +1,403 @@ +/* + 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. + */ + +// +// Functions to load JPEG files from a buffer, based on jdatasrc.c +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "jpeg.h" + +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#include "ifilesystem.h" + +#include "imagelib.h" + +typedef unsigned char byte; + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + int src_size; + JOCTET *src_buffer; + + 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; + +const int INPUT_BUF_SIZE = 4096; /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +static void my_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. + */ + +static boolean my_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->src_buffer, nbytes); + src->src_buffer += nbytes; + src->src_size -= nbytes; + + if (nbytes <= 0) { + if (src->start_of_file) { /* Treat empty input file as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); + } + WARNMS(cinfo, JWRN_JPEG_EOF); + /* Insert a fake EOI marker */ + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } + + 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. + */ + +static void my_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) my_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. + */ + +static void my_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. + */ + +static void jpeg_buffer_src(j_decompress_ptr cinfo, void *buffer, 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 = my_init_source; + src->pub.fill_input_buffer = my_fill_input_buffer; + src->pub.skip_input_data = my_skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = my_term_source; + src->src_buffer = (JOCTET *) buffer; + src->src_size = bufsize; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} + +// ============================================================================= + +static char errormsg[JMSG_LENGTH_MAX]; + +typedef struct my_jpeg_error_mgr { + struct jpeg_error_mgr pub; // "public" fields + jmp_buf setjmp_buffer; // for return to caller +} bt_jpeg_error_mgr; + +static void my_jpeg_error_exit(j_common_ptr cinfo) +{ + my_jpeg_error_mgr *myerr = (bt_jpeg_error_mgr *) cinfo->err; + + (*cinfo->err->format_message)(cinfo, errormsg); + + longjmp(myerr->setjmp_buffer, 1); +} + +// stash a scanline +static void j_putRGBScanline(unsigned char *jpegline, int widthPix, unsigned char *outBuf, int row) +{ + int offset = row * widthPix * 4; + int count; + + for (count = 0; count < widthPix; count++) { + unsigned char iRed, iBlu, iGrn; + unsigned char *oRed, *oBlu, *oGrn, *oAlp; + + iRed = *(jpegline + count * 3 + 0); + iGrn = *(jpegline + count * 3 + 1); + iBlu = *(jpegline + count * 3 + 2); + + oRed = outBuf + offset + count * 4 + 0; + oGrn = outBuf + offset + count * 4 + 1; + oBlu = outBuf + offset + count * 4 + 2; + oAlp = outBuf + offset + count * 4 + 3; + + *oRed = iRed; + *oGrn = iGrn; + *oBlu = iBlu; + *oAlp = 255; + } +} + +// stash a scanline +static void j_putRGBAScanline(unsigned char *jpegline, int widthPix, unsigned char *outBuf, int row) +{ + int offset = row * widthPix * 4; + int count; + + for (count = 0; count < widthPix; count++) { + unsigned char iRed, iBlu, iGrn, iAlp; + unsigned char *oRed, *oBlu, *oGrn, *oAlp; + + iRed = *(jpegline + count * 4 + 0); + iGrn = *(jpegline + count * 4 + 1); + iBlu = *(jpegline + count * 4 + 2); + iAlp = *(jpegline + count * 4 + 3); + + oRed = outBuf + offset + count * 4 + 0; + oGrn = outBuf + offset + count * 4 + 1; + oBlu = outBuf + offset + count * 4 + 2; + oAlp = outBuf + offset + count * 4 + 3; + + *oRed = iRed; + *oGrn = iGrn; + *oBlu = iBlu; + + //!\todo fix jpeglib, it leaves alpha channel uninitialised +#if 1 + (void) iAlp; + *oAlp = 255; +#else + *oAlp = iAlp; +#endif + } +} + +// stash a gray scanline +static void j_putGrayScanlineToRGB(unsigned char *jpegline, int widthPix, unsigned char *outBuf, int row) +{ + int offset = row * widthPix * 4; + int count; + + for (count = 0; count < widthPix; count++) { + unsigned char iGray; + unsigned char *oRed, *oBlu, *oGrn, *oAlp; + + // get our grayscale value + iGray = *(jpegline + count); + + oRed = outBuf + offset + count * 4; + oGrn = outBuf + offset + count * 4 + 1; + oBlu = outBuf + offset + count * 4 + 2; + oAlp = outBuf + offset + count * 4 + 3; + + *oRed = iGray; + *oGrn = iGray; + *oBlu = iGray; + *oAlp = 255; + } +} + +static Image *LoadJPGBuff_(const void *src_buffer, int src_size) +{ + struct jpeg_decompress_struct cinfo; + struct my_jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_jpeg_error_exit; + + if (setjmp(jerr.setjmp_buffer)) { //< TODO: use c++ exceptions instead of setjmp/longjmp to handle errors + globalErrorStream() << "WARNING: JPEG library error: " << errormsg << "\n"; + jpeg_destroy_decompress(&cinfo); + return 0; + } + + jpeg_create_decompress(&cinfo); + jpeg_buffer_src(&cinfo, const_cast( src_buffer ), src_size); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + int row_stride = cinfo.output_width * cinfo.output_components; + + RGBAImage *image = new RGBAImage(cinfo.output_width, cinfo.output_height); + + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_scanlines(&cinfo, buffer, 1); + + if (cinfo.out_color_components == 4) { + j_putRGBAScanline(buffer[0], cinfo.output_width, image->getRGBAPixels(), cinfo.output_scanline - 1); + } else if (cinfo.out_color_components == 3) { + j_putRGBScanline(buffer[0], cinfo.output_width, image->getRGBAPixels(), cinfo.output_scanline - 1); + } else if (cinfo.out_color_components == 1) { + j_putGrayScanlineToRGB(buffer[0], cinfo.output_width, image->getRGBAPixels(), cinfo.output_scanline - 1); + } + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return image; +} + +Image *LoadJPG(ArchiveFile &file) +{ + ScopedArchiveBuffer buffer(file); + return LoadJPGBuff_(buffer.buffer, static_cast( buffer.length )); +} diff --git a/plugins/image/jpeg.h b/plugins/image/jpeg.h new file mode 100644 index 0000000..2295297 --- /dev/null +++ b/plugins/image/jpeg.h @@ -0,0 +1,40 @@ +/* + 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_JPEG_H ) +#define INCLUDED_JPEG_H + +class Image; + +class ArchiveFile; + +Image *LoadJPG(ArchiveFile &file); + +#endif diff --git a/plugins/image/ktx.cpp b/plugins/image/ktx.cpp new file mode 100644 index 0000000..9d22053 --- /dev/null +++ b/plugins/image/ktx.cpp @@ -0,0 +1,431 @@ +/* + Copyright (C) 2015, SiPlus, Chasseur de bots. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "ktx.h" + +#include + +#include "bytestreamutils.h" +#include "etclib.h" +#include "ifilesystem.h" +#include "imagelib.h" + + +const int KTX_TYPE_UNSIGNED_BYTE = 0x1401; +const int KTX_TYPE_UNSIGNED_SHORT_4_4_4_4 = 0x8033; +const int KTX_TYPE_UNSIGNED_SHORT_5_5_5_1 = 0x8034; +const int KTX_TYPE_UNSIGNED_SHORT_5_6_5 = 0x8363; + +const int KTX_FORMAT_ALPHA = 0x1906; +const int KTX_FORMAT_RGB = 0x1907; +const int KTX_FORMAT_RGBA = 0x1908; +const int KTX_FORMAT_LUMINANCE = 0x1909; +const int KTX_FORMAT_LUMINANCE_ALPHA = 0x190A; +const int KTX_FORMAT_BGR = 0x80E0; +const int KTX_FORMAT_BGRA = 0x80E1; + +const int KTX_FORMAT_ETC1_RGB8 = 0x8D64; + +class KTX_Decoder { +public: + virtual ~KTX_Decoder() = default; + + virtual void Decode(PointerInputStream &istream, byte *out) = 0; + + virtual unsigned int GetPixelSize() = 0; +}; + +class KTX_Decoder_A8 : public KTX_Decoder { +public: + virtual void Decode(PointerInputStream &istream, byte *out) + { + out[0] = out[1] = out[2] = 0; + out[3] = istream_read_byte(istream); + } + + virtual unsigned int GetPixelSize() + { + return 1; + } +}; + +class KTX_Decoder_RGB8 : public KTX_Decoder { +public: + virtual void Decode(PointerInputStream &istream, byte *out) + { + istream.read(out, 3); + out[3] = 255; + } + + virtual unsigned int GetPixelSize() + { + return 3; + } +}; + +class KTX_Decoder_RGBA8 : public KTX_Decoder { +public: + virtual void Decode(PointerInputStream &istream, byte *out) + { + istream.read(out, 4); + } + + virtual unsigned int GetPixelSize() + { + return 4; + } +}; + +class KTX_Decoder_L8 : public KTX_Decoder { +public: + virtual void Decode(PointerInputStream &istream, byte *out) + { + byte l = istream_read_byte(istream); + out[0] = out[1] = out[2] = l; + out[3] = 255; + } + + virtual unsigned int GetPixelSize() + { + return 1; + } +}; + +class KTX_Decoder_LA8 : public KTX_Decoder { +public: + virtual void Decode(PointerInputStream &istream, byte *out) + { + byte la[2]; + istream.read(la, 2); + out[0] = out[1] = out[2] = la[0]; + out[3] = la[1]; + } + + virtual unsigned int GetPixelSize() + { + return 2; + } +}; + +class KTX_Decoder_BGR8 : public KTX_Decoder { +public: + virtual void Decode(PointerInputStream &istream, byte *out) + { + byte bgr[3]; + istream.read(bgr, 3); + out[0] = bgr[2]; + out[1] = bgr[1]; + out[2] = bgr[0]; + out[3] = 255; + } + + virtual unsigned int GetPixelSize() + { + return 3; + } +}; + +class KTX_Decoder_BGRA8 : public KTX_Decoder { +public: + virtual void Decode(PointerInputStream &istream, byte *out) + { + byte bgra[4]; + istream.read(bgra, 4); + out[0] = bgra[2]; + out[1] = bgra[1]; + out[2] = bgra[0]; + out[3] = bgra[3]; + } + + virtual unsigned int GetPixelSize() + { + return 4; + } +}; + +class KTX_Decoder_RGBA4 : public KTX_Decoder { +protected: + bool m_bigEndian; +public: + KTX_Decoder_RGBA4(bool bigEndian) : m_bigEndian(bigEndian) + {} + + virtual void Decode(PointerInputStream &istream, byte *out) + { + uint16_t rgba; + if (m_bigEndian) { + rgba = istream_read_uint16_be(istream); + } else { + rgba = istream_read_uint16_le(istream); + } + int r = (rgba >> 12) & 0xf; + int g = (rgba >> 8) & 0xf; + int b = (rgba >> 4) & 0xf; + int a = rgba & 0xf; + out[0] = (r << 4) | r; + out[1] = (g << 4) | g; + out[2] = (b << 4) | b; + out[3] = (a << 4) | a; + } + + virtual unsigned int GetPixelSize() + { + return 2; + } +}; + +class KTX_Decoder_RGBA5 : public KTX_Decoder { +protected: + bool m_bigEndian; +public: + KTX_Decoder_RGBA5(bool bigEndian) : m_bigEndian(bigEndian) + {} + + virtual void Decode(PointerInputStream &istream, byte *out) + { + uint16_t rgba; + if (m_bigEndian) { + rgba = istream_read_uint16_be(istream); + } else { + rgba = istream_read_uint16_le(istream); + } + int r = (rgba >> 11) & 0x1f; + int g = (rgba >> 6) & 0x1f; + int b = (rgba >> 1) & 0x1f; + out[0] = (r << 3) | (r >> 2); + out[1] = (g << 3) | (g >> 2); + out[2] = (b << 3) | (b >> 2); + out[3] = (rgba & 1) * 255; + } + + virtual unsigned int GetPixelSize() + { + return 2; + } +}; + +class KTX_Decoder_RGB5 : public KTX_Decoder { +protected: + bool m_bigEndian; +public: + KTX_Decoder_RGB5(bool bigEndian) : m_bigEndian(bigEndian) + {} + + virtual void Decode(PointerInputStream &istream, byte *out) + { + uint16_t rgb; + if (m_bigEndian) { + rgb = istream_read_uint16_be(istream); + } else { + rgb = istream_read_uint16_le(istream); + } + int r = (rgb >> 11) & 0x1f; + int g = (rgb >> 5) & 0x3f; + int b = rgb & 0x1f; + out[0] = (r << 3) | (r >> 2); + out[1] = (g << 2) | (g >> 4); + out[2] = (b << 3) | (b >> 2); + out[3] = 255; + } + + virtual unsigned int GetPixelSize() + { + return 2; + } +}; + +static void KTX_DecodeETC1(PointerInputStream &istream, Image &image) +{ + unsigned int width = image.getWidth(), height = image.getHeight(); + unsigned int stride = width * 4; + byte *pixbuf = image.getRGBAPixels(); + byte etc[8], rgba[64]; + + for (unsigned int y = 0; y < height; y += 4, pixbuf += stride * 4) { + unsigned int blockrows = height - y; + if (blockrows > 4) { + blockrows = 4; + } + + byte *p = pixbuf; + for (unsigned int x = 0; x < width; x += 4, p += 16) { + istream.read(etc, 8); + ETC_DecodeETC1Block(etc, rgba, qtrue); + + unsigned int blockrowsize = width - x; + if (blockrowsize > 4) { + blockrowsize = 4; + } + blockrowsize *= 4; + for (unsigned int blockrow = 0; blockrow < blockrows; blockrow++) { + memcpy(p + blockrow * stride, rgba + blockrow * 16, blockrowsize); + } + } + } +} + +Image *LoadKTXBuff(PointerInputStream &istream) +{ + byte identifier[12]; + istream.read(identifier, 12); + if (memcmp(identifier, "\xABKTX 11\xBB\r\n\x1A\n", 12)) { + globalErrorStream() << "LoadKTX: Image has the wrong identifier\n"; + return 0; + } + + bool bigEndian = (istream_read_uint32_le(istream) == 0x01020304); + + unsigned int type; + if (bigEndian) { + type = istream_read_uint32_be(istream); + } else { + type = istream_read_uint32_le(istream); + } + + // For compressed textures, the format is in glInternalFormat. + // For uncompressed textures, it's in glBaseInternalFormat. + istream.seek((type ? 3 : 2) * sizeof(uint32_t)); + unsigned int format; + if (bigEndian) { + format = istream_read_uint32_be(istream); + } else { + format = istream_read_uint32_le(istream); + } + if (!type) { + istream.seek(sizeof(uint32_t)); + } + + unsigned int width, height; + if (bigEndian) { + width = istream_read_uint32_be(istream); + height = istream_read_uint32_be(istream); + } else { + width = istream_read_uint32_le(istream); + height = istream_read_uint32_le(istream); + } + if (!width) { + globalErrorStream() << "LoadKTX: Image has zero width\n"; + return 0; + } + if (!height) { + height = 1; + } + + // Skip the key/values and load the first 2D image in the texture. + // Since KTXorientation is only a hint and has no effect on the texture data and coordinates, it must be ignored. + istream.seek(4 * sizeof(uint32_t)); + unsigned int bytesOfKeyValueData; + if (bigEndian) { + bytesOfKeyValueData = istream_read_uint32_be(istream); + } else { + bytesOfKeyValueData = istream_read_uint32_le(istream); + } + istream.seek(bytesOfKeyValueData + sizeof(uint32_t)); + + RGBAImage *image = new RGBAImage(width, height); + + if (type) { + KTX_Decoder *decoder = NULL; + switch (type) { + case KTX_TYPE_UNSIGNED_BYTE: + switch (format) { + case KTX_FORMAT_ALPHA: + decoder = new KTX_Decoder_A8(); + break; + case KTX_FORMAT_RGB: + decoder = new KTX_Decoder_RGB8(); + break; + case KTX_FORMAT_RGBA: + decoder = new KTX_Decoder_RGBA8(); + break; + case KTX_FORMAT_LUMINANCE: + decoder = new KTX_Decoder_L8(); + break; + case KTX_FORMAT_LUMINANCE_ALPHA: + decoder = new KTX_Decoder_LA8(); + break; + case KTX_FORMAT_BGR: + decoder = new KTX_Decoder_BGR8(); + break; + case KTX_FORMAT_BGRA: + decoder = new KTX_Decoder_BGRA8(); + break; + } + break; + case KTX_TYPE_UNSIGNED_SHORT_4_4_4_4: + if (format == KTX_FORMAT_RGBA) { + decoder = new KTX_Decoder_RGBA4(bigEndian); + } + break; + case KTX_TYPE_UNSIGNED_SHORT_5_5_5_1: + if (format == KTX_FORMAT_RGBA) { + decoder = new KTX_Decoder_RGBA5(bigEndian); + } + break; + case KTX_TYPE_UNSIGNED_SHORT_5_6_5: + if (format == KTX_FORMAT_RGB) { + decoder = new KTX_Decoder_RGB5(bigEndian); + } + break; + } + + if (!decoder) { + globalErrorStream() << "LoadKTX: Image has an unsupported pixel type " << type << " or format " << format + << "\n"; + image->release(); + return 0; + } + + unsigned int inRowLength = width * decoder->GetPixelSize(); + unsigned int inPadding = ((inRowLength + 3) & ~3) - inRowLength; + byte *out = image->getRGBAPixels(); + for (unsigned int y = 0; y < height; y++) { + for (unsigned int x = 0; x < width; x++, out += 4) { + decoder->Decode(istream, out); + } + + if (inPadding) { + istream.seek(inPadding); + } + } + + delete decoder; + } else { + switch (format) { + case KTX_FORMAT_ETC1_RGB8: + KTX_DecodeETC1(istream, *image); + break; + default: + globalErrorStream() << "LoadKTX: Image has an unsupported compressed format " << format << "\n"; + image->release(); + return 0; + } + } + + return image; +} + +Image *LoadKTX(ArchiveFile &file) +{ + ScopedArchiveBuffer buffer(file); + PointerInputStream istream(buffer.buffer); + return LoadKTXBuff(istream); +} diff --git a/plugins/image/ktx.h b/plugins/image/ktx.h new file mode 100644 index 0000000..f9b0553 --- /dev/null +++ b/plugins/image/ktx.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 2015, SiPlus, Chasseur de bots. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_KTX_H ) +#define INCLUDED_KTX_H + +class Image; + +class ArchiveFile; + +Image *LoadKTX(ArchiveFile &file); + +#endif diff --git a/plugins/image/pcx.cpp b/plugins/image/pcx.cpp new file mode 100644 index 0000000..8d658a7 --- /dev/null +++ b/plugins/image/pcx.cpp @@ -0,0 +1,211 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "pcx.h" + +#include "ifilesystem.h" + +typedef unsigned char byte; + +#include + +#include "imagelib.h" +#include "bytestreamutils.h" + +/* + ================================================================= + + PCX LOADING + + ================================================================= + */ + +typedef struct { + unsigned char manufacturer; + unsigned char version; + unsigned char encoding; + unsigned char bits_per_pixel; + unsigned short xmin, ymin, xmax, ymax; + unsigned short hres, vres; + unsigned char palette[48]; + unsigned char reserved; + unsigned char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + unsigned char filler[58]; + unsigned char data; // unbounded +} pcx_t; + +/* + ============== + LoadPCX + ============== + */ + +struct PCXRLEPacket { + byte data; + int length; +}; + +inline void ByteStream_readPCXRLEPacket(PointerInputStream &inputStream, PCXRLEPacket &packet) +{ + byte d; + inputStream.read(&d, 1); + if ((d & 0xC0) == 0xC0) { + packet.length = d & 0x3F; + inputStream.read(&packet.data, 1); + } else { + packet.length = 1; + packet.data = d; + } +} + +void LoadPCXBuff(byte *buffer, std::size_t len, byte **pic, byte **palette, int *width, int *height) +{ + *pic = 0; + + pcx_t pcx; + int x, y, lsize; + byte *out, *pix; + + /* parse the PCX file */ + + PointerInputStream inputStream(buffer); + + pcx.manufacturer = istream_read_byte(inputStream); + pcx.version = istream_read_byte(inputStream); + pcx.encoding = istream_read_byte(inputStream); + pcx.bits_per_pixel = istream_read_byte(inputStream); + pcx.xmin = istream_read_int16_le(inputStream); + pcx.ymin = istream_read_int16_le(inputStream); + pcx.xmax = istream_read_int16_le(inputStream); + pcx.ymax = istream_read_int16_le(inputStream); + pcx.hres = istream_read_int16_le(inputStream); + pcx.vres = istream_read_int16_le(inputStream); + inputStream.read(pcx.palette, 48); + pcx.reserved = istream_read_byte(inputStream); + pcx.color_planes = istream_read_byte(inputStream); + pcx.bytes_per_line = istream_read_int16_le(inputStream); + pcx.palette_type = istream_read_int16_le(inputStream); + inputStream.read(pcx.filler, 58); + + + if (pcx.manufacturer != 0x0a + || pcx.version != 5 + || pcx.encoding != 1 + || pcx.bits_per_pixel != 8) { + return; + } + + if (width) { + *width = pcx.xmax + 1; + } + if (height) { + *height = pcx.ymax + 1; + } + + if (!pic) { + return; + } + + out = (byte *) malloc((pcx.ymax + 1) * (pcx.xmax + 1)); + + *pic = out; + pix = out; + + /* RR2DO2: pcx fix */ + lsize = pcx.color_planes * pcx.bytes_per_line; + + /* go scanline by scanline */ + for (y = 0; y <= pcx.ymax; y++, pix += pcx.xmax + 1) { + /* do a scanline */ + for (x = 0; x <= pcx.xmax;) { + /* RR2DO2 */ + PCXRLEPacket packet; + ByteStream_readPCXRLEPacket(inputStream, packet); + + while (packet.length-- > 0) { + pix[x++] = packet.data; + } + } + + /* RR2DO2: discard any other data */ + PCXRLEPacket packet; + while (x < lsize) { + ByteStream_readPCXRLEPacket(inputStream, packet); + x++; + } + while (packet.length-- > 0) { + x++; + } + } + + /* validity check */ + if (std::size_t(inputStream.get() - buffer) > len) { + *pic = 0; + } + + if (palette) { + *palette = (byte *) malloc(768); + memcpy(*palette, buffer + len - 768, 768); + } +} + +/* + ============== + LoadPCX32 + ============== + */ +Image *LoadPCX32Buff(byte *buffer, std::size_t length) +{ + byte *palette; + byte *pic8; + int i, c, p, width, height; + byte *pic32; + + LoadPCXBuff(buffer, length, &pic8, &palette, &width, &height); + if (!pic8) { + return 0; + } + + RGBAImage *image = new RGBAImage(width, height); + c = (width) * (height); + pic32 = image->getRGBAPixels(); + for (i = 0; i < c; i++) { + p = pic8[i]; + pic32[0] = palette[p * 3]; + pic32[1] = palette[p * 3 + 1]; + pic32[2] = palette[p * 3 + 2]; + pic32[3] = 255; + pic32 += 4; + } + + free(pic8); + free(palette); + + return image; +} + +Image *LoadPCX32(ArchiveFile &file) +{ + ScopedArchiveBuffer buffer(file); + return LoadPCX32Buff(buffer.buffer, buffer.length); +} diff --git a/plugins/image/pcx.h b/plugins/image/pcx.h new file mode 100644 index 0000000..81f10e4 --- /dev/null +++ b/plugins/image/pcx.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_PCX_H ) +#define INCLUDED_PCX_H + +class Image; + +class ArchiveFile; + +Image *LoadPCX32(ArchiveFile &file); + +#endif diff --git a/plugins/image/tga.cpp b/plugins/image/tga.cpp new file mode 100644 index 0000000..c430700 --- /dev/null +++ b/plugins/image/tga.cpp @@ -0,0 +1,433 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "tga.h" + +#include "ifilesystem.h" +#include "iarchive.h" +#include "idatastream.h" + +typedef unsigned char byte; + +#include + +#include "generic/bitfield.h" +#include "imagelib.h" +#include "bytestreamutils.h" + +// represents x,y origin of tga image being decoded +class Flip00 {}; // no flip +class Flip01 {}; // vertical flip only +class Flip10 {}; // horizontal flip only +class Flip11 {}; // both + +template +void image_decode(PointerInputStream &istream, PixelDecoder &decode, RGBAImage &image, const Flip00 &) +{ + RGBAPixel *end = image.pixels + (image.height * image.width); + for (RGBAPixel *row = end; row != image.pixels; row -= image.width) { + for (RGBAPixel *pixel = row - image.width; pixel != row; ++pixel) { + decode(istream, *pixel); + } + } +} + +template +void image_decode(PointerInputStream &istream, PixelDecoder &decode, RGBAImage &image, const Flip01 &) +{ + RGBAPixel *end = image.pixels + (image.height * image.width); + for (RGBAPixel *row = image.pixels; row != end; row += image.width) { + for (RGBAPixel *pixel = row; pixel != row + image.width; ++pixel) { + decode(istream, *pixel); + } + } +} + +template +void image_decode(PointerInputStream &istream, PixelDecoder &decode, RGBAImage &image, const Flip10 &) +{ + RGBAPixel *end = image.pixels + (image.height * image.width); + for (RGBAPixel *row = end; row != image.pixels; row -= image.width) { + for (RGBAPixel *pixel = row; pixel != row - image.width;) { + decode(istream, *--pixel); + } + } +} + +template +void image_decode(PointerInputStream &istream, PixelDecoder &decode, RGBAImage &image, const Flip11 &) +{ + RGBAPixel *end = image.pixels + (image.height * image.width); + for (RGBAPixel *row = image.pixels; row != end; row += image.width) { + for (RGBAPixel *pixel = row + image.width; pixel != row;) { + decode(istream, *--pixel); + } + } +} + +inline void istream_read_gray(PointerInputStream &istream, RGBAPixel &pixel) +{ + istream.read(&pixel.blue, 1); + pixel.red = pixel.green = pixel.blue; + pixel.alpha = 0xff; +} + +inline void istream_read_rgb(PointerInputStream &istream, RGBAPixel &pixel) +{ + istream.read(&pixel.blue, 1); + istream.read(&pixel.green, 1); + istream.read(&pixel.red, 1); + pixel.alpha = 0xff; +} + +inline void istream_read_rgba(PointerInputStream &istream, RGBAPixel &pixel) +{ + istream.read(&pixel.blue, 1); + istream.read(&pixel.green, 1); + istream.read(&pixel.red, 1); + istream.read(&pixel.alpha, 1); +} + +class TargaDecodeGrayPixel { +public: + void operator()(PointerInputStream &istream, RGBAPixel &pixel) + { + istream_read_gray(istream, pixel); + } +}; + +template +void targa_decode_grayscale(PointerInputStream &istream, RGBAImage &image, const Flip &flip) +{ + TargaDecodeGrayPixel decode; + image_decode(istream, decode, image, flip); +} + +class TargaDecodeRGBPixel { +public: + void operator()(PointerInputStream &istream, RGBAPixel &pixel) + { + istream_read_rgb(istream, pixel); + } +}; + +template +void targa_decode_rgb(PointerInputStream &istream, RGBAImage &image, const Flip &flip) +{ + TargaDecodeRGBPixel decode; + image_decode(istream, decode, image, flip); +} + +class TargaDecodeRGBAPixel { +public: + void operator()(PointerInputStream &istream, RGBAPixel &pixel) + { + istream_read_rgba(istream, pixel); + } +}; + +template +void targa_decode_rgba(PointerInputStream &istream, RGBAImage &image, const Flip &flip) +{ + TargaDecodeRGBAPixel decode; + image_decode(istream, decode, image, flip); +} + +typedef byte TargaPacket; +typedef byte TargaPacketSize; + +inline void targa_packet_read_istream(TargaPacket &packet, PointerInputStream &istream) +{ + istream.read(&packet, 1); +} + +inline bool targa_packet_is_rle(const TargaPacket &packet) +{ + return (packet & 0x80) != 0; +} + +inline TargaPacketSize targa_packet_size(const TargaPacket &packet) +{ + return 1 + (packet & 0x7f); +} + + +class TargaDecodeGrayPixelRLE { + TargaPacketSize m_packetSize; + RGBAPixel m_pixel; + TargaPacket m_packet; +public: + TargaDecodeGrayPixelRLE() : m_packetSize(0) + { + } + + void operator()(PointerInputStream &istream, RGBAPixel &pixel) + { + if (m_packetSize == 0) { + targa_packet_read_istream(m_packet, istream); + m_packetSize = targa_packet_size(m_packet); + + if (targa_packet_is_rle(m_packet)) { + istream_read_gray(istream, m_pixel); + } + } + + if (targa_packet_is_rle(m_packet)) { + pixel = m_pixel; + } else { + istream_read_gray(istream, pixel); + } + + --m_packetSize; + } +}; + +template +void targa_decode_rle_grayscale(PointerInputStream &istream, RGBAImage &image, const Flip &flip) +{ + TargaDecodeGrayPixelRLE decode; + image_decode(istream, decode, image, flip); +} + +class TargaDecodeRGBPixelRLE { + TargaPacketSize m_packetSize; + RGBAPixel m_pixel; + TargaPacket m_packet; +public: + TargaDecodeRGBPixelRLE() : m_packetSize(0) + { + } + + void operator()(PointerInputStream &istream, RGBAPixel &pixel) + { + if (m_packetSize == 0) { + targa_packet_read_istream(m_packet, istream); + m_packetSize = targa_packet_size(m_packet); + + if (targa_packet_is_rle(m_packet)) { + istream_read_rgb(istream, m_pixel); + } + } + + if (targa_packet_is_rle(m_packet)) { + pixel = m_pixel; + } else { + istream_read_rgb(istream, pixel); + } + + --m_packetSize; + } +}; + +template +void targa_decode_rle_rgb(PointerInputStream &istream, RGBAImage &image, const Flip &flip) +{ + TargaDecodeRGBPixelRLE decode; + image_decode(istream, decode, image, flip); +} + +class TargaDecodeRGBAPixelRLE { + TargaPacketSize m_packetSize; + RGBAPixel m_pixel; + TargaPacket m_packet; +public: + TargaDecodeRGBAPixelRLE() : m_packetSize(0) + { + } + + void operator()(PointerInputStream &istream, RGBAPixel &pixel) + { + if (m_packetSize == 0) { + targa_packet_read_istream(m_packet, istream); + m_packetSize = targa_packet_size(m_packet); + + if (targa_packet_is_rle(m_packet)) { + istream_read_rgba(istream, m_pixel); + } + } + + if (targa_packet_is_rle(m_packet)) { + pixel = m_pixel; + } else { + istream_read_rgba(istream, pixel); + } + + --m_packetSize; + } +}; + +template +void targa_decode_rle_rgba(PointerInputStream &istream, RGBAImage &image, const Flip &flip) +{ + TargaDecodeRGBAPixelRLE decode; + image_decode(istream, decode, image, flip); +} + +struct TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +}; + +inline void targa_header_read_istream(TargaHeader &targa_header, PointerInputStream &istream) +{ + targa_header.id_length = istream_read_byte(istream); + targa_header.colormap_type = istream_read_byte(istream); + targa_header.image_type = istream_read_byte(istream); + + targa_header.colormap_index = istream_read_int16_le(istream); + targa_header.colormap_length = istream_read_int16_le(istream); + targa_header.colormap_size = istream_read_byte(istream); + targa_header.x_origin = istream_read_int16_le(istream); + targa_header.y_origin = istream_read_int16_le(istream); + targa_header.width = istream_read_int16_le(istream); + targa_header.height = istream_read_int16_le(istream); + targa_header.pixel_size = istream_read_byte(istream); + targa_header.attributes = istream_read_byte(istream); + + if (targa_header.id_length != 0) { + istream.seek(targa_header.id_length); // skip TARGA image comment + } +} + +template +class ScopeDelete { + Type *m_value; + + ScopeDelete(const ScopeDelete &); + + ScopeDelete &operator=(const ScopeDelete &); + +public: + ScopeDelete(Type *value) : m_value(value) + { + } + + ~ScopeDelete() + { + delete m_value; + } + + Type *get_pointer() const + { + return m_value; + } +}; + +template +Image *Targa_decodeImageData(const TargaHeader &targa_header, PointerInputStream &istream, const Flip &flip) +{ + RGBAImage *image = new RGBAImage(targa_header.width, targa_header.height); + + if (targa_header.image_type == 2 || targa_header.image_type == 3) { + switch (targa_header.pixel_size) { + case 8: + targa_decode_grayscale(istream, *image, flip); + break; + case 24: + targa_decode_rgb(istream, *image, flip); + break; + case 32: + targa_decode_rgba(istream, *image, flip); + break; + default: + globalErrorStream() << "LoadTGA: illegal pixel_size '" << targa_header.pixel_size << "'\n"; + image->release(); + return 0; + } + } else if (targa_header.image_type == 10 || targa_header.image_type == 11) { + switch (targa_header.pixel_size) { + case 8: + targa_decode_rle_grayscale(istream, *image, flip); + break; + case 24: + targa_decode_rle_rgb(istream, *image, flip); + break; + case 32: + targa_decode_rle_rgba(istream, *image, flip); + break; + default: + globalErrorStream() << "LoadTGA: illegal pixel_size '" << targa_header.pixel_size << "'\n"; + image->release(); + return 0; + } + } + + return image; +} + +const unsigned int TGA_FLIP_HORIZONTAL = 0x10; +const unsigned int TGA_FLIP_VERTICAL = 0x20; + +Image *LoadTGABuff(const byte *buffer) +{ + PointerInputStream istream(buffer); + TargaHeader targa_header; + + targa_header_read_istream(targa_header, istream); + + if (targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 && + targa_header.image_type != 11) { + globalErrorStream() << "LoadTGA: TGA type " << targa_header.image_type << " not supported\n"; + globalErrorStream() << "LoadTGA: Only type 2 (RGB), 3 (gray), 10 (RGB), and 11 (gray) TGA images supported\n"; + return 0; + } + + if (targa_header.colormap_type != 0) { + globalErrorStream() << "LoadTGA: colormaps not supported\n"; + return 0; + } + + if (((targa_header.image_type == 2 || targa_header.image_type == 10) && targa_header.pixel_size != 32 && + targa_header.pixel_size != 24) || + ((targa_header.image_type == 3 || targa_header.image_type == 11) && targa_header.pixel_size != 8)) { + globalErrorStream() << "LoadTGA: Only 32, 24 or 8 bit images supported\n"; + return 0; + } + + if (!bitfield_enabled(targa_header.attributes, TGA_FLIP_HORIZONTAL) + && !bitfield_enabled(targa_header.attributes, TGA_FLIP_VERTICAL)) { + return Targa_decodeImageData(targa_header, istream, Flip00()); + } + if (!bitfield_enabled(targa_header.attributes, TGA_FLIP_HORIZONTAL) + && bitfield_enabled(targa_header.attributes, TGA_FLIP_VERTICAL)) { + return Targa_decodeImageData(targa_header, istream, Flip01()); + } + if (bitfield_enabled(targa_header.attributes, TGA_FLIP_HORIZONTAL) + && !bitfield_enabled(targa_header.attributes, TGA_FLIP_VERTICAL)) { + return Targa_decodeImageData(targa_header, istream, Flip10()); + } + if (bitfield_enabled(targa_header.attributes, TGA_FLIP_HORIZONTAL) + && bitfield_enabled(targa_header.attributes, TGA_FLIP_VERTICAL)) { + return Targa_decodeImageData(targa_header, istream, Flip11()); + } + + // unreachable + return 0; +} + +Image *LoadTGA(ArchiveFile &file) +{ + ScopedArchiveBuffer buffer(file); + return LoadTGABuff(buffer.buffer); +} diff --git a/plugins/image/tga.h b/plugins/image/tga.h new file mode 100644 index 0000000..226a997 --- /dev/null +++ b/plugins/image/tga.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_TGA_H ) +#define INCLUDED_TGA_H + +class Image; + +class ArchiveFile; + +Image *LoadTGA(ArchiveFile &file); + +#endif diff --git a/plugins/iqmmodel/CMakeLists.txt b/plugins/iqmmodel/CMakeLists.txt new file mode 100644 index 0000000..e371ae7 --- /dev/null +++ b/plugins/iqmmodel/CMakeLists.txt @@ -0,0 +1,4 @@ +radiant_plugin(iqmmodel + iqm.cpp iqm.h + plugin.cpp plugin.h + ) diff --git a/plugins/iqmmodel/iqm.cpp b/plugins/iqmmodel/iqm.cpp new file mode 100644 index 0000000..466effc --- /dev/null +++ b/plugins/iqmmodel/iqm.cpp @@ -0,0 +1,313 @@ +/* + Copyright (C) 2001-2006, William Joseph. + Copyright (C) 2010-2014 COR Entertainment, LLC. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "iqm.h" + +#include "ifilesystem.h" +#include "imodel.h" + +#include "imagelib.h" +#include "bytestreamutils.h" + +#include "../md3model/model.h" + +typedef unsigned char byte; + +/* + ======================================================================== + + .IQM triangle model file format + + ======================================================================== + */ + +typedef struct { + float s; + float t; +} iqmSt_t; + +void istream_read_iqmSt( PointerInputStream &inputStream, iqmSt_t &st ){ + st.s = istream_read_float32_le( inputStream ); + st.t = istream_read_float32_le( inputStream ); +} + +typedef struct { + unsigned int indices[3]; +} iqmTriangle_t; + +void istream_read_iqmTriangle( PointerInputStream &inputStream, iqmTriangle_t &triangle ){ + triangle.indices[0] = istream_read_int32_le( inputStream ); + triangle.indices[1] = istream_read_int32_le( inputStream ); + triangle.indices[2] = istream_read_int32_le( inputStream ); +} + +typedef struct { + float v[3]; +} iqmPos_t; + +void istream_read_iqmPos( PointerInputStream &inputStream, iqmPos_t &iqmPos ){ + iqmPos.v[0] = istream_read_float32_le( inputStream ); + iqmPos.v[1] = istream_read_float32_le( inputStream ); + iqmPos.v[2] = istream_read_float32_le( inputStream ); +} + +const int IQM_POSITION = 0; +const int IQM_TEXCOORD = 1; +const int IQM_NORMAL = 2; +const int IQM_TANGENT = 3; +const int IQM_BLENDINDEXES = 4; +const int IQM_BLENDWEIGHTS = 5; +const int IQM_COLOR = 6; +const int IQM_CUSTOM = 0x10; + +const int IQM_BYTE = 0; +const int IQM_UBYTE = 1; +const int IQM_SHORT = 2; +const int IQM_USHORT = 3; +const int IQM_INT = 4; +const int IQM_UINT = 5; +const int IQM_HALF = 6; +const int IQM_FLOAT = 7; +const int IQM_DOUBLE = 8; + +// animflags +const int IQM_LOOP = 1; + +typedef struct iqmHeader_s { + byte id[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_neighbors; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; +} iqmHeader_t; + +void istream_read_iqmHeader( PointerInputStream &inputStream, iqmHeader_t &header ){ + inputStream.read( header.id, 16 ); +#define READINT( x ) header.x = istream_read_int32_le( inputStream ); + READINT( version ) + READINT( filesize ) + READINT( flags ) + READINT( num_text ) + READINT( ofs_text ) + READINT( num_meshes ) + READINT( ofs_meshes ) + READINT( num_vertexarrays ) + READINT( num_vertexes ) + READINT( ofs_vertexarrays ) + READINT( num_triangles ) + READINT( ofs_triangles ) + READINT( ofs_neighbors ) + READINT( num_joints ) + READINT( ofs_joints ) + READINT( num_frames ) + READINT( num_framechannels ) + READINT( ofs_frames ) + READINT( ofs_bounds ) + READINT( num_comment ) + READINT( ofs_comment ) + READINT( num_extensions ) + READINT( ofs_extensions ) +#undef READINT +} + +typedef struct iqmmesh_s { + unsigned int name; + unsigned int material; + unsigned int first_vertex; + unsigned int num_vertexes; + unsigned int first_triangle; + unsigned int num_triangles; +} iqmmesh_t; + +void istream_read_iqmMesh( PointerInputStream &inputStream, iqmmesh_t &iqmmesh ){ +#define READUINT( x ) iqmmesh.x = istream_read_uint32_le( inputStream ); + READUINT( name ) + READUINT( material ) + READUINT( first_vertex ) + READUINT( num_vertexes ) + READUINT( first_triangle ) + READUINT( num_triangles ) +#undef READUINT +} + +typedef struct iqmvertexarray_s { + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; +} iqmvertexarray_t; + +void istream_read_iqmVertexarray( PointerInputStream &inputStream, iqmvertexarray_t &vertexarray ){ +#define READINT( x ) vertexarray.x = istream_read_int32_le( inputStream ); + READINT( type ) + READINT( flags ) + READINT( format ) + READINT( size ) + READINT( offset ) +#undef READINT +} + +ArbitraryMeshVertex IQMVertex_construct( const iqmPos_t *pos, const iqmPos_t *norm, const iqmSt_t *st ){ + return ArbitraryMeshVertex( + Vertex3f( pos->v[0], pos->v[1], pos->v[2] ), + Normal3f( norm->v[0], norm->v[1], norm->v[2] ), + TexCoord2f( st->s, st->t ) + ); +} + +void IQMSurface_read( Model &model, const byte *buffer, ArchiveFile &file ){ + iqmHeader_t header; + { + PointerInputStream inputStream( buffer ); + istream_read_iqmHeader( inputStream, header ); + } + + int ofs_position = -1, ofs_st = -1, ofs_normal = -1; + PointerInputStream vaStream( buffer + header.ofs_vertexarrays ); + for ( unsigned int i = 0; i < header.num_vertexarrays; i++ ) { + iqmvertexarray_t va; + istream_read_iqmVertexarray( vaStream, va ); + + switch ( va.type ) { + case IQM_POSITION: + if ( va.format == IQM_FLOAT && va.size == 3 ) { + ofs_position = va.offset; + } + break; + case IQM_TEXCOORD: + if ( va.format == IQM_FLOAT && va.size == 2 ) { + ofs_st = va.offset; + } + break; + case IQM_NORMAL: + if ( va.format == IQM_FLOAT && va.size == 3 ) { + ofs_normal = va.offset; + } + break; + } + } + + PointerInputStream posStream( buffer + ofs_position ); + Array iqmPos( header.num_vertexes ); + for ( Array::iterator i = iqmPos.begin(); i != iqmPos.end(); ++i ) { + istream_read_iqmPos( posStream, *i ); + } + + PointerInputStream normStream( buffer + ofs_normal ); + Array iqmNorm( header.num_vertexes ); + for ( Array::iterator i = iqmNorm.begin(); i != iqmNorm.end(); ++i ) { + istream_read_iqmPos( normStream, *i ); + } + + Array iqmSt( header.num_vertexes ); + PointerInputStream stStream( buffer + ofs_st ); + for ( Array::iterator i = iqmSt.begin(); i != iqmSt.end(); ++i ) { + istream_read_iqmSt( stStream, *i ); + } + + PointerInputStream iqmMesh( buffer + header.ofs_meshes ); + for ( unsigned int m = 0; m < header.num_meshes; m++ ) { + Surface &surface = model.newSurface(); + + iqmmesh_t iqmmesh; + istream_read_iqmMesh( iqmMesh, iqmmesh ); + + bool material_found = false; + // if not malformed data neither missing string + if ( iqmmesh.material <= header.num_text && iqmmesh.material > 0 ) { + char *material; + material = (char*) buffer + header.ofs_text + iqmmesh.material; + + if ( material[0] != '\0' ) { + surface.setShader( material ); + material_found = true; + } + } + + if ( !material_found ) { + // empty string will trigger "textures/shader/notex" on display + surface.setShader( "" ); + } + + UniqueVertexBuffer inserter( surface.vertices() ); + inserter.reserve( iqmmesh.num_vertexes ); + + surface.indices().reserve( iqmmesh.num_vertexes ); + + unsigned int triangle_offset = header.ofs_triangles + iqmmesh.first_triangle * sizeof( iqmTriangle_t ); + PointerInputStream triangleStream( buffer + triangle_offset ); + for ( unsigned int i = 0; i < iqmmesh.num_triangles; ++i ) { + iqmTriangle_t triangle; + istream_read_iqmTriangle( triangleStream, triangle ); + for ( int j = 0; j < 3; j++ ) { + surface.indices().insert( inserter.insert( IQMVertex_construct( + &iqmPos[triangle.indices[j]], + &iqmNorm[triangle.indices[j]], + &iqmSt[triangle.indices[j]] ) ) ); + } + } + + surface.updateAABB(); + } +} + +void IQMModel_read( Model &model, const byte *buffer, ArchiveFile &file ){ + IQMSurface_read( model, buffer, file ); + model.updateAABB(); +} + +scene::Node &IQMModel_new( const byte *buffer, ArchiveFile &file ){ + ModelNode *modelNode = new ModelNode(); + IQMModel_read( modelNode->model(), buffer, file ); + return modelNode->node(); +} + +scene::Node &IQMModel_default(){ + ModelNode *modelNode = new ModelNode(); + Model_constructNull( modelNode->model() ); + return modelNode->node(); +} + +scene::Node &IQMModel_fromBuffer( unsigned char *buffer, ArchiveFile &file ){ + if ( memcmp( buffer, "INTERQUAKEMODEL", 16 ) ) { + globalErrorStream() << "IQM read error: incorrect ident\n"; + return IQMModel_default(); + } + else { + return IQMModel_new( buffer, file ); + } +} + +scene::Node &loadIQMModel( ArchiveFile &file ){ + ScopedArchiveBuffer buffer( file ); + return IQMModel_fromBuffer( buffer.buffer, file ); +} diff --git a/plugins/iqmmodel/iqm.h b/plugins/iqmmodel/iqm.h new file mode 100644 index 0000000..8f48f3b --- /dev/null +++ b/plugins/iqmmodel/iqm.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_IQM_H ) +#define INCLUDED_IQM_H + +namespace scene { class Node; } +class ArchiveFile; + +scene::Node &loadIQMModel( ArchiveFile &file ); + + +#endif diff --git a/plugins/iqmmodel/modeliqm.def b/plugins/iqmmodel/modeliqm.def new file mode 100644 index 0000000..5791cd7 --- /dev/null +++ b/plugins/iqmmodel/modeliqm.def @@ -0,0 +1,7 @@ +; modeliqm.def : Declares the module parameters for the DLL. + +LIBRARY "MODELIQM" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/iqmmodel/plugin.cpp b/plugins/iqmmodel/plugin.cpp new file mode 100644 index 0000000..5d0999c --- /dev/null +++ b/plugins/iqmmodel/plugin.cpp @@ -0,0 +1,101 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "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 "iscriplib.h" + +#include "modulesystem/singletonmodule.h" +#include "typesystem.h" + +#include "iqm.h" + + +class IQMModelLoader : public ModelLoader { +public: +scene::Node &loadModel( ArchiveFile &file ){ + return loadIQMModel( file ); +} +}; + +/* InterQuake Model Format */ +class ModelDependencies : + public GlobalFileSystemModuleRef, + public GlobalOpenGLModuleRef, + public GlobalUndoModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalSelectionModuleRef, + public GlobalFiletypesModuleRef { +}; + +class ModelIQMAPI : public TypeSystemRef { + IQMModelLoader m_modeliqm; + public: + typedef ModelLoader Type; + + STRING_CONSTANT( Name, "iqm" ); + + ModelIQMAPI(){ + GlobalFiletypesModule::getTable().addType( Type::Name(), Name(), filetype_t( "InterQuake Models", "*.iqm" ) ); + } + + ModelLoader *getTable(){ + return &m_modeliqm; + } +}; +typedef SingletonModule ModelIQMModule; +ModelIQMModule g_ModelIQMModule; + +/* Vera Visions Model Format */ +class ModelVVMAPI : public TypeSystemRef { + IQMModelLoader m_modeliqm; + public: + typedef ModelLoader Type; + + STRING_CONSTANT( Name, "vvm" ); + + ModelVVMAPI(){ + GlobalFiletypesModule::getTable().addType( Type::Name(), Name(), filetype_t( "Vera Visions Model", "*.vvm" ) ); + } + + ModelLoader *getTable(){ + return &m_modeliqm; + } +}; +typedef SingletonModule ModelVVMModule; +ModelVVMModule g_ModelVVMModule; + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer &server ){ + initialiseModule( server ); + + g_ModelIQMModule.selfRegister(); + g_ModelVVMModule.selfRegister(); +} diff --git a/plugins/iqmmodel/plugin.h b/plugins/iqmmodel/plugin.h new file mode 100644 index 0000000..c6bfe6c --- /dev/null +++ b/plugins/iqmmodel/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_SAMPLE_H ) +#define INCLUDED_SAMPLE_H + +#endif diff --git a/plugins/mapq3/CMakeLists.txt b/plugins/mapq3/CMakeLists.txt new file mode 100644 index 0000000..f60c5f9 --- /dev/null +++ b/plugins/mapq3/CMakeLists.txt @@ -0,0 +1,9 @@ +radiant_plugin(mapq3 + parse.cpp parse.h + plugin.cpp + write.cpp write.h + ) + +target_include_directories(mapq3 + PRIVATE $ + ) diff --git a/plugins/mapq3/mapq3.def b/plugins/mapq3/mapq3.def new file mode 100644 index 0000000..ddbbaf5 --- /dev/null +++ b/plugins/mapq3/mapq3.def @@ -0,0 +1,7 @@ +; mapq3.def : Declares the module parameters for the DLL. + +LIBRARY "MAPQ3" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/mapq3/parse.cpp b/plugins/mapq3/parse.cpp new file mode 100644 index 0000000..4e76886 --- /dev/null +++ b/plugins/mapq3/parse.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 "parse.h" + +#include + +#include "ientity.h" +#include "ibrush.h" +#include "ipatch.h" +#include "ieclass.h" +#include "iscriplib.h" +#include "scenelib.h" +#include "string/string.h" +#include "stringio.h" +#include "eclasslib.h" + +inline MapImporter *Node_getMapImporter(scene::Node &node) +{ + return NodeTypeCast::cast(node); +} + + +typedef std::list > KeyValues; + +NodeSmartReference g_nullNode(NewNullNode()); + + +NodeSmartReference Entity_create(EntityCreator &entityTable, EntityClass *entityClass, const KeyValues &keyValues) +{ + scene::Node &entity(entityTable.createEntity(entityClass)); + for (KeyValues::const_iterator i = keyValues.begin(); i != keyValues.end(); ++i) { + Node_getEntity(entity)->setKeyValue((*i).first.c_str(), (*i).second.c_str()); + } + return NodeSmartReference(entity); +} + +NodeSmartReference +Entity_parseTokens(Tokeniser &tokeniser, EntityCreator &entityTable, const PrimitiveParser &parser, int index) +{ + NodeSmartReference entity(g_nullNode); + KeyValues keyValues; + const char *classname = ""; + + int count_primitives = 0; + while (1) { + tokeniser.nextLine(); + const char *token = tokeniser.getToken(); + if (token == 0) { + Tokeniser_unexpectedError(tokeniser, token, "#entity-token"); + return g_nullNode; + } + if (!strcmp(token, "}")) { // end entity + if (entity == g_nullNode) { + // entity does not have brushes + entity = Entity_create(entityTable, GlobalEntityClassManager().findOrInsert(classname, false), + keyValues); + } + return entity; + } else if (!strcmp(token, "{")) { // begin primitive + if (entity == g_nullNode) { + // entity has brushes + entity = Entity_create(entityTable, GlobalEntityClassManager().findOrInsert(classname, true), + keyValues); + } + + tokeniser.nextLine(); + + NodeSmartReference primitive(parser.parsePrimitive(tokeniser)); + if (primitive == g_nullNode || !Node_getMapImporter(primitive)->importTokens(tokeniser)) { + globalErrorStream() << "brush " << count_primitives << ": parse error\n"; + return g_nullNode; + } + + scene::Traversable *traversable = Node_getTraversable(entity); + if (Node_getEntity(entity)->isContainer() && traversable != 0) { + traversable->insert(primitive); + } else { + globalErrorStream() << "entity " << index << ": type " << classname << ": discarding brush " + << count_primitives << "\n"; + } + ++count_primitives; + } else // epair + { + CopiedString key(token); + token = tokeniser.getToken(); + if (token == 0) { + Tokeniser_unexpectedError(tokeniser, token, "#epair-value"); + return g_nullNode; + } + keyValues.push_back(KeyValues::value_type(key, token)); + if (string_equal(key.c_str(), "classname")) { + classname = keyValues.back().second.c_str(); + } + } + } + // unreachable code + return g_nullNode; +} + +void Map_Read(scene::Node &root, Tokeniser &tokeniser, EntityCreator &entityTable, const PrimitiveParser &parser) +{ + int count_entities = 0; + for (;;) { + tokeniser.nextLine(); + if (!tokeniser.getToken()) { // { or 0 + break; + } + + NodeSmartReference entity(Entity_parseTokens(tokeniser, entityTable, parser, count_entities)); + + if (entity == g_nullNode) { + globalErrorStream() << "entity " << count_entities << ": parse error\n"; + return; + } + + Node_getTraversable(root)->insert(entity); + + ++count_entities; + } +} diff --git a/plugins/mapq3/parse.h b/plugins/mapq3/parse.h new file mode 100644 index 0000000..50424f2 --- /dev/null +++ b/plugins/mapq3/parse.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_PARSE_H ) +#define INCLUDED_PARSE_H + +#include "imap.h" + +class BrushCreator; + +class PatchCreator; + +class PrimitiveParser { +public: + virtual scene::Node &parsePrimitive(Tokeniser &tokeniser) const = 0; +}; + +void Map_Read(scene::Node &root, Tokeniser &tokeniser, EntityCreator &entityTable, const PrimitiveParser &parser); + +namespace scene { + class Node; +} + +#include "generic/referencecounted.h" + +typedef SmartReference > NodeSmartReference; + +extern NodeSmartReference g_nullNode; + +#endif diff --git a/plugins/mapq3/plugin.cpp b/plugins/mapq3/plugin.cpp new file mode 100644 index 0000000..a33061c --- /dev/null +++ b/plugins/mapq3/plugin.cpp @@ -0,0 +1,146 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" +#include "ibrush.h" +#include "ipatch.h" +#include "ifiletypes.h" +#include "ieclass.h" +#include "qerplugin.h" + +#include "scenelib.h" +#include "string/string.h" +#include "stringio.h" +#include "generic/constant.h" + +#include "modulesystem/singletonmodule.h" + +#include "parse.h" +#include "write.h" + +class MapDependencies : + public GlobalRadiantModuleRef, + public GlobalBrushModuleRef, + public GlobalPatchModuleRef, + public GlobalFiletypesModuleRef, + public GlobalScripLibModuleRef, + public GlobalEntityClassManagerModuleRef, + public GlobalSceneGraphModuleRef { +public: + MapDependencies() : + GlobalBrushModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("brushtypes")), + GlobalPatchModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("patchtypes")), + GlobalEntityClassManagerModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entityclass")) + { + } +}; + +class MapQ3API : public TypeSystemRef, public MapFormat, public PrimitiveParser { + mutable bool detectedFormat; +public: + typedef MapFormat Type; + + STRING_CONSTANT(Name, "worldspawn"); + + MapQ3API() + { + GlobalFiletypesModule::getTable().addType(Type::Name(), Name(), + filetype_t("WorldSpawn maps", "*.map", true, true, true)); + GlobalFiletypesModule::getTable().addType(Type::Name(), Name(), + filetype_t("WorldSpawn region", "*.reg", true, true, true)); + GlobalFiletypesModule::getTable().addType(Type::Name(), Name(), + filetype_t("WorldSpawn compiled maps", "*.bsp", false, true, false)); + } + + MapFormat *getTable() + { + return this; + } + + scene::Node &parsePrimitive(Tokeniser &tokeniser) const + { + const char *primitive = tokeniser.getToken(); + if (primitive != 0) { + if (string_equal(primitive, "patchDef2") || string_equal(primitive, "patchDef2WS")) { + return GlobalPatchModule::getTable().createPatch(false, false); + } if (string_equal(primitive, "patchDef3") || string_equal(primitive, "patchDef3WS")) { + return GlobalPatchModule::getTable().createPatch(true, false); + } if (string_equal(primitive, "patchDefWS")) { + return GlobalPatchModule::getTable().createPatch(false, true); + } + + if (!detectedFormat) + { + EBrushType fmt; + if (string_equal(primitive, "brushDef")) + fmt = eBrushTypeQuake3BP; + else if (string_equal(primitive, "(")) + { + if (1)//tokeniser.getLine + fmt = eBrushTypeQuake3Valve; + else + fmt = eBrushTypeQuake3; + } + if (fmt != GlobalBrushModule::getTable().getBrushType()) + GlobalBrushModule::getTable().setBrushType(fmt); + } + + switch(GlobalBrushModule::getTable().getBrushType()) + { + default: + tokeniser.ungetToken(); // ( + case eBrushTypeQuake3BP: + return GlobalBrushModule::getTable().createBrush(); + } + } + + Tokeniser_unexpectedError(tokeniser, primitive, "#quake3-primitive"); + return g_nullNode; + } + + void readGraph(scene::Node &root, TextInputStream &inputStream, EntityCreator &entityTable) const + { + detectedFormat = false; + wrongFormat = false; + Tokeniser &tokeniser = GlobalScripLibModule::getTable().m_pfnNewSimpleTokeniser(inputStream); + Map_Read(root, tokeniser, entityTable, *this); + tokeniser.release(); + } + + void writeGraph(scene::Node &root, GraphTraversalFunc traverse, TextOutputStream &outputStream) const + { + TokenWriter &writer = GlobalScripLibModule::getTable().m_pfnNewSimpleTokenWriter(outputStream); + Map_Write(root, traverse, writer, false); + writer.release(); + } +}; + +typedef SingletonModule MapQ3Module; + +MapQ3Module g_MapQ3Module; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer &server) +{ + initialiseModule(server); + + g_MapQ3Module.selfRegister(); +} diff --git a/plugins/mapq3/write.cpp b/plugins/mapq3/write.cpp new file mode 100644 index 0000000..f37b5b0 --- /dev/null +++ b/plugins/mapq3/write.cpp @@ -0,0 +1,118 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "write.h" + +#include "ientity.h" +#include "iscriplib.h" +#include "scenelib.h" + +inline MapExporter *Node_getMapExporter(scene::Node &node) +{ + return NodeTypeCast::cast(node); +} + + +static std::size_t g_count_entities; +static std::size_t g_count_brushes; + + +void Entity_ExportTokens(const Entity &entity, TokenWriter &writer) +{ + g_count_brushes = 0; + + class WriteKeyValue : public Entity::Visitor { + TokenWriter &m_writer; + public: + WriteKeyValue(TokenWriter &writer) + : m_writer(writer) + { + } + + void visit(const char *key, const char *value) + { + m_writer.writeString(key); + m_writer.writeString(value); + m_writer.nextLine(); + } + + } visitor(writer); + + entity.forEachKeyValue(visitor); +} + +class WriteTokensWalker : public scene::Traversable::Walker { + mutable Stack m_stack; + TokenWriter &m_writer; + bool m_ignorePatches; +public: + WriteTokensWalker(TokenWriter &writer, bool ignorePatches) + : m_writer(writer), m_ignorePatches(ignorePatches) + { + } + + bool pre(scene::Node &node) const + { + m_stack.push(false); + + Entity *entity = Node_getEntity(node); + if (entity != 0) { + m_writer.writeToken("//"); + m_writer.writeToken("entity"); + m_writer.writeUnsigned(g_count_entities++); + m_writer.nextLine(); + + m_writer.writeToken("{"); + m_writer.nextLine(); + m_stack.top() = true; + + Entity_ExportTokens(*entity, m_writer); + } else { + MapExporter *exporter = Node_getMapExporter(node); + if (exporter != 0 + && !(m_ignorePatches && Node_isPatch(node))) { + m_writer.writeToken("//"); + m_writer.writeToken("brush"); + m_writer.writeUnsigned(g_count_brushes++); + m_writer.nextLine(); + + exporter->exportTokens(m_writer); + } + } + + return true; + } + + void post(scene::Node &node) const + { + if (m_stack.top()) { + m_writer.writeToken("}"); + m_writer.nextLine(); + } + m_stack.pop(); + } +}; + +void Map_Write(scene::Node &root, GraphTraversalFunc traverse, TokenWriter &writer, bool ignorePatches) +{ + g_count_entities = 0; + traverse(root, WriteTokensWalker(writer, ignorePatches)); +} diff --git a/plugins/mapq3/write.h b/plugins/mapq3/write.h new file mode 100644 index 0000000..0fbfce0 --- /dev/null +++ b/plugins/mapq3/write.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_WRITE_H ) +#define INCLUDED_WRITE_H + +#include "imap.h" + +void Map_Write(scene::Node &root, GraphTraversalFunc traverse, TokenWriter &writer, bool ignorePatches); + +#endif diff --git a/plugins/md3model/model.h b/plugins/md3model/model.h new file mode 100644 index 0000000..f8c46fc --- /dev/null +++ b/plugins/md3model/model.h @@ -0,0 +1,634 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 + +#include "globaldefs.h" +#include "cullable.h" +#include "renderable.h" +#include "selectable.h" +#include "modelskin.h" + +#include "math/frustum.h" +#include "string/string.h" +#include "generic/static.h" +#include "stream/stringstream.h" +#include "os/path.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)); + } + } +}; + +inline VertexPointer vertexpointer_arbitrarymeshvertex(const ArbitraryMeshVertex *array) +{ + return VertexPointer(VertexPointer::pointer(&array->vertex), sizeof(ArbitraryMeshVertex)); +} + +inline void parseTextureName(CopiedString &name, const char *token) +{ + StringOutputStream cleaned(256); + cleaned << PathCleaned(token); + name = StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str())); // remove extension +} + +// generic renderable triangle surface +class Surface : + public OpenGLRenderable { +public: + typedef VertexBuffer vertices_t; + typedef IndexBuffer indices_t; +private: + + AABB m_aabb_local; + CopiedString m_shader; + Shader *m_state; + + vertices_t m_vertices; + indices_t m_indices; + + void CaptureShader() + { + m_state = GlobalShaderCache().capture(m_shader.c_str()); + } + + void ReleaseShader() + { + GlobalShaderCache().release(m_shader.c_str()); + } + +public: + + Surface() + : m_shader(""), m_state(0) + { + CaptureShader(); + } + + ~Surface() + { + ReleaseShader(); + } + + vertices_t &vertices() + { + return m_vertices; + } + + indices_t &indices() + { + return m_indices; + } + + void setShader(const char *name) + { + ReleaseShader(); + parseTextureName(m_shader, name); + CaptureShader(); + } + + const char *getShader() const + { + return m_shader.c_str(); + } + + Shader *getState() const + { + return m_state; + } + + void updateAABB() + { + m_aabb_local = AABB(); + for (vertices_t::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) { + aabb_extend_by_point_safe(m_aabb_local, reinterpret_cast((*i).vertex )); + } + + + for (Surface::indices_t::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 (Surface::vertices_t::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) { + vector3_normalise(reinterpret_cast((*i).tangent )); + vector3_normalise(reinterpret_cast((*i).bitangent )); + } + } + + 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 0 + glBegin(GL_LINES); + + for (VertexBuffer::const_iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) { + Vector3 normal = vector3_added(vertex3f_to_vector3((*i).vertex), + vector3_scaled(normal3f_to_vector3((*i).normal), 8)); + glVertex3fv(vertex3f_to_array((*i).vertex)); + glVertex3fv(vector3_to_array(normal)); + } + 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; + test.TestTriangles( + vertexpointer_arbitrarymeshvertex(m_vertices.data()), + IndexPointer(m_indices.data(), IndexPointer::index_type(m_indices.size())), + best + ); + if (best.valid()) { + selector.addIntersection(best); + } + } +}; + +// generic model node +class Model : + public Cullable, + public Bounded { + typedef std::vector surfaces_t; + surfaces_t m_surfaces; + + AABB m_aabb_local; +public: + Callback m_lightsChanged; + + ~Model() + { + 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(); + } + + Surface &newSurface() + { + m_surfaces.push_back(new Surface); + return *m_surfaces.back(); + } + + void updateAABB() + { + m_aabb_local = AABB(); + for (surfaces_t::iterator i = m_surfaces.begin(); i != m_surfaces.end(); ++i) { + aabb_extend_by_aabb_safe(m_aabb_local, (*i)->localAABB()); + } + } + + 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 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); + } + } + } +}; + +inline void Surface_addLight(const Surface &surface, VectorLightList &lights, const Matrix4 &localToWorld, + const RendererLight &light) +{ + if (light.testAABB(aabb_for_oriented_aabb(surface.localAABB(), localToWorld))) { + lights.addLight(light); + } +} + +class ModelInstance : + 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; + } + }; + + Model &m_model; + + 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; +public: + + typedef LazyStatic StaticTypeCasts; + + Bounded &get(NullType) + { + return m_model; + } + + Cullable &get(NullType) + { + return m_model; + } + + void lightsChanged() + { + m_lightList->lightsChanged(); + } + + typedef MemberCaller LightsChangedCaller; + + void constructRemaps() + { + ModelSkin *skin = NodeTypeCast::cast(path().parent()); + if (skin != 0 && skin->realised()) { + SurfaceRemaps::iterator j = m_skins.begin(); + for (Model::const_iterator i = m_model.begin(); i != m_model.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() + { + 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() + { + ASSERT_MESSAGE(m_skins.size() == m_model.size(), "ERROR"); + destroyRemaps(); + constructRemaps(); + } + + ModelInstance(const scene::Path &path, scene::Instance *parent, Model &model) : + Instance(path, parent, this, StaticTypeCasts::instance().get()), + m_model(model), + m_surfaceLightLists(m_model.size()), + m_skins(m_model.size()) + { + m_lightList = &GlobalShaderCache().attach(*this); + m_model.m_lightsChanged = LightsChangedCaller(*this); + + Instance::setTransformChangedCallback(LightsChangedCaller(*this)); + + constructRemaps(); + } + + ~ModelInstance() + { + destroyRemaps(); + + Instance::setTransformChangedCallback(Callback()); + + m_model.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 (Model::const_iterator i = m_model.begin(); i != m_model.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_model.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 (Model::const_iterator i = m_model.begin(); i != m_model.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 ModelNode : 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; + Model m_model; +public: + + typedef LazyStatic StaticTypeCasts; + + ModelNode() : m_node(this, this, StaticTypeCasts::instance().get()) + { + } + + Model &model() + { + return m_model; + } + + void release() + { + delete this; + } + + scene::Node &node() + { + return m_node; + } + + scene::Instance *create(const scene::Path &path, scene::Instance *parent) + { + return new ModelInstance(path, parent, m_model); + } + + 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 void +Surface_constructQuad(Surface &surface, const Vector3 &a, const Vector3 &b, const Vector3 &c, const Vector3 &d, + const Vector3 &normal) +{ + surface.vertices().push_back( + ArbitraryMeshVertex( + vertex3f_for_vector3(a), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_topleft) + ) + ); + surface.vertices().push_back( + ArbitraryMeshVertex( + vertex3f_for_vector3(b), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_topright) + ) + ); + surface.vertices().push_back( + ArbitraryMeshVertex( + vertex3f_for_vector3(c), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_botright) + ) + ); + surface.vertices().push_back( + ArbitraryMeshVertex( + vertex3f_for_vector3(d), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_botleft) + ) + ); +} + +inline void Model_constructNull(Model &model) +{ + Surface &surface = model.newSurface(); + + AABB aabb(Vector3(0, 0, 0), Vector3(8, 8, 8)); + + Vector3 points[8]; + aabb_corners(aabb, points); + + surface.vertices().reserve(24); + + Surface_constructQuad(surface, points[2], points[1], points[5], points[6], aabb_normals[0]); + Surface_constructQuad(surface, points[1], points[0], points[4], points[5], aabb_normals[1]); + Surface_constructQuad(surface, points[0], points[1], points[2], points[3], aabb_normals[2]); + Surface_constructQuad(surface, points[0], points[3], points[7], points[4], aabb_normals[3]); + Surface_constructQuad(surface, points[3], points[2], points[6], points[7], aabb_normals[4]); + Surface_constructQuad(surface, points[7], points[6], points[5], points[4], aabb_normals[5]); + + surface.indices().reserve(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, + }; + + for (RenderIndex *i = indices; i != indices + (sizeof(indices) / sizeof(RenderIndex)); ++i) { + surface.indices().insert(*i); + } + + surface.setShader(""); + + surface.updateAABB(); + + model.updateAABB(); +} + +#endif diff --git a/plugins/model/CMakeLists.txt b/plugins/model/CMakeLists.txt new file mode 100644 index 0000000..4a23e28 --- /dev/null +++ b/plugins/model/CMakeLists.txt @@ -0,0 +1,7 @@ +radiant_plugin(model + model.cpp model.h + plugin.cpp + ) + +target_include_directories(model PRIVATE picomodel) +target_link_libraries(model PRIVATE picomodel) diff --git a/plugins/model/bitmaps/model_reload_entity.bmp b/plugins/model/bitmaps/model_reload_entity.bmp new file mode 100644 index 0000000000000000000000000000000000000000..800a0dfe79835b00f9c6a0c27240ca23c87825cc GIT binary patch literal 308 zcmZvWK@NZ*3O3Zt3U_lI9h=RDiQ`>&?YKZyP7PGNLqrQ_W HYM!J5T+jt+ literal 0 HcmV?d00001 diff --git a/plugins/model/bitmaps/picomodel.bmp b/plugins/model/bitmaps/picomodel.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0d9c88e071f2d3e0f046fbee0350fb611c745119 GIT binary patch literal 192660 zcmeFaXP4yGwWe8g#q#OB3-5gd0tg_y_b$DwtSs+XMHa0{N|e94EpxlqT+Qm9b!X;7 z_skEN{y2YXe!@HlCo`BuNhC!h6%7^=d2nC?5x{=2_q)w0cK-eUd-!c-mOMna{Z#o?h+85)g^m|o#nhd=BOMk8U>=?VlAMs543 zym=VT)ZL-fvc)|;zcevDJ3c;j>EfmH=U;#I)z@Br`Q?{hdgKmXe4^vanDaro@?(kt|^i|iM| z^87^4U!A_?AE&T+*tqp1nyz{xN+7HS6S=5fY3$yqZr!MFUDv7ur8-dS>qfnwsWsx6 zoGY4&XWQObTFW=ndF!3!UUc72fBW#ceAvZ~Y81;-+We=-=6);Af-`aPp?A^QlyFceQ zZ+MgGs9I7A-R|Dq;o$={Q;#aSOr=xk9#*&RHh1n;I(tT?6Gz*q4UOhzzJHLaZ78{J zG}|b4cGc>J(b_4s4~)iMxqG1GnlYmoP8x|+R?~7yMD@6R%gdIT`IV`;1-m~QQnMCs zXlh~Q>g4qKOP5|f|N1Mhz6y|jCm)8dcXxDMKR7t}5fi@f7eE`+G!2RQu4Be)=g*&o z3fg?B(Nb<=c@`u_I6-679>K$3ZG63_@^g-^Y~AZV{iJdIjd-b}6nok3jsEo~&CTO@ zvJi>raW$K1Ze%)p#o>+G@Hm6ok*h}1nKBejk1J{{t_1uMo5f?ZIYU~Z*xS#vwxgLwG+kP-xo75=#-^q( zUb@Q9`O2%WpoD$SHGj`G`ThPMZIdqq40hmG0$-&teMg^xi?6?qp20#|CrJMYA!v!! z*Ir{5uwa4+ee}wG=mJILQo_j>sv3V4Tt@)&Re!_Yz4v=hevx0lTRnKtyZP4b_x`Z` z;G@#;FqNz4^38~%Co`q;U^~;Q-oH8#_=W+GKkv%OShJKZ}-);80F!|v0M+Ydjk zZar-8J+5xt%{8_XsbV~#gu>x)LP_Ndab4H)g+jGgZ*3)wQb^0?+q?CxyXnSmNUu49 zNrx}IwC0$eTf$~R=lDTk#`5wqW;)ZCk((d#KHm?_FxiX~J!1qIGyn=_Hh?E$AF$#z zAVfgPS#?INKO){c?@r(@*NR-Y;(4Y219a_tx)rAO9lTIY^e; zg~q@ah}&G=NHmo+%37&c>K+xk*Nel0WWJHE?d$E^>rZ~M{^rjMySEbUoyP5VTQ}d# zw)d*r_tLeERIZjv8F41G$!sAMRibLTxqh&7`z?%}RIw4#ih5&vaQ)5p&b@f98BFFK zzSz>5ZESMp^-GsQQ;fAQ_YJ=Ddo3(1fCN8c#uvh&#EjQWrTsg4*QK6u{mxF~3;scHa1;v~?eTV*BC8 z?b{z4JzOFEbYmmm+DPVW@k}ugGaUYiI~Wedlw`53xAy`{CSBj&y!X??5B_lfU;bR! zI*Js!#qE2oyYH6w?spEK=K6=RY$ItDVu_?L5by^>9=G4=@uv!v%3wcN+sjqAtp0?O zuWVj_xOw~S>h6PFdpDk`x`MGKi+$PZn4Fy-o1D5jHhJmFI1uv6`PW~5TxzL?XdgT$+wd>CP0(zanv+!zaVz zXK|b&TX&51q2Ao9Y~Rnc_w${-e0Qh3ei$z{+%e7J2|JvAe^m7)4ZA<$ji!`hH`hBX z46cW>HJ_4JYU|abcU!lAQtTg=_a79uZ-$IA-dBgy<8-<09=PD^Muws!Fh{we0t{EeA$mZ8n=fV#b%jj7-}2PGbhX)(h|%pu+3t@t|B3;2}T*p%>y$X1IRH~No% z*SP&jad^FQ{o&4&k4hVd&QRRz3q`_Fr`PB92NRiG80EvLbT^L&2X~77{cLq3Rc!ke z!x>IynuEsXVKQHjB-4>Z${SIs=0$2bf02o5s z5ZZ)bGC&X15H_1kso*@o29_tj*;il2k};u0f{19d0}ubVh!&qwJA`i{mM&|RUNl>2 z-*{tq|0i4bp5^a|RDQ(>y+<$-X@rRk}M!wLBDYnkQ; zoZy6s8LwZyc;V6o{3x%Ye@FH(STg3fngm`S#}iqr(Ruw07_1np+9I9E@cn zYDFs$zEuxK3?<*F?BDC%|7mIKkx|HtUE$=yvi;(fYy6xb@wqecEid}R^B3309k$tQySuwzC^SFt9l#8`&HCN)!jHrk zfkwmu2DAvNTbP(};mXC=FPp>$W($ZS@B-u8L=A}z=p0~*h(LG+Qt#_pC0}17A$B}b zO5{53m~Qch9O0-t6bnXEu~a2n+wSZ=DhzMsx9@b0o;25Q>bXv(v(rDgqZZmBqXC~0 zNak(6h$Ef~6cchljP^L8iJMFZ2sL4~CCFZX7--^$)A< z9VJ^&cXv|lz4GvuRv0>c%G~_&_!vB?Nl2rrz53cqFF((bp}CzSIQ{dY)8&no!c6-w zr21R$n4FyaF%*0sL@85dX-MN#kbycHiWU87`NfkSljpNSsH_Ph}^zu+I zHNv^7k}k#7v{9&4`+Kk&dTS%s+bItZ)3pJ*NTInAQZk`LMlE%l$8Qdw{G5|-KlxRA z_d#RxxOMb6zy0Xw-9PO<{cUsSelk;adxMiOq}Q%tE0T=B@61ng_QrD~<$Sht)aG}h zjfN*QJ@ZE!ZTg2bzzq0|A8F9fhYhJ`{KIa_SNNdMkwTtHG=-4?0&;*5S4h4RfFWjp z1Z*(jM6jU(GvGYXFql~}d(8jBBoWW5IgJ`}RW3)OKU)2Cs|gt5XweYeqn%~rFMB?- z!?|8d|;{O$ov@zj@HQ^G@sD zN4xL;vAq2N4@>L%)_KclF(MxY*W)S6S!xuUcG$j5R))< z`91j6xk22S3j%M>qx^m0QQ!CuKwY(3{gDR!e9YM2+qYRQ-}sOJ4W96GFhe|MX1o@t z095dpGGDuL8MFW@Ml)D)$>cyK4T|Y4c#OYrlt`$M=VZymKBH+Q{j!Iv`c$_tw~h8Z z+xQGN{H;}TV6!o(T+Vrts$4+o?1 zR4ScFBolGX<%!@U-Fo`7d%ycXoA*D_YkOLnpw5^b7iyutar068`lI#R@7MR=?BD&k ze*7+eU5)&dbkiBjMU85;x0k7{lk1kNwo1K?a&J4^=&OW#skw4zud{!5c_^APbvvQ;iWXSc7g zX>M)~J%co?&zGAY>{fo9ot@pa)s?UC+x}w?5kh7LG>FETNy;T+jbU-oBr_ytj9tCL z3`mUB3mPV7pjn7XXceYsWCRhMb@`&%Y6lE(`XhFi-{TF$!V#a>8wdpb zA>lV|>_2E7zcak^{@$&(8oeD->4N}KbJ#t+zj5!~M?e4f?#=h}{oAF%&F0pfL>9B9 zX;e`u)>EZnd3cnn4b*bI*c}!-n>vut9O(H*p}sx1`E>LC+iHE7X{^J4__bm*S0MpE zSMRsBjyG;REp`q<$x%SD;6uO^Q)HG<+a(Rwegv`E8`P5a?fA5C~g-Jk1q>L z=O03H1cO0XjUO$Pzlw_}VVzEQdTR0?0|0;Tr<{n?Utq>aM3HGg0Nw(?K(_!b#;<`G zC>4U(kT#113=|P*gYci31HlF+I;Gkndjv=b6T>7%u~#I&G5Wl}vjNkW-5wNP*P_v(yA5ASh3g_maViM9>2>Mas@(b4)>DP zvFh|J*_`fB)EkakobJWdH9M-4q6L$>H=c3@qOL$Vq-ZNv``iN6K4zw8=cZ<6ua1p@ zBG<+zE}^)9CD5$j8ia&GAzgCJQ z9x<5=L1v6F1Hh2P1`XjW0ZYu_uJgjbD&s>%pqGr=5WyVch4zy0q@2!(y_9l3?(c*Q zu{&Sk3^a%@Bsx#edGZCtlTS!hv9qV;TYe*(ZuPxEHIPt^TC;X=|HiXlS6lnOfU@cg zSbXpc%}^rij~RAiGp!E0%i{`0eNi=$O2;)dmellIGv7McxbuGh`kT4_4H8LmwViC| zX7ljv%HT%2+%_tM`p*6K{#~O!h?V=DTW=q|_uIkUcd9$bdab8qYDq2Y4+PzzxIGXj zPQB1Q2&PI}v6(8j-NA%2td;r)@!~+MlXt)6RpDyax8D49?f8A_f28saHB$yAmRHum zjHMOJ{L+%$5YA2Wf(&a0lKR@9&`!LEAvb5XngTa7s4T&5- zsvQzPPBCNboBi3pk+TVTdS1gAiLBTnj0n;nUd6cwU@)IGMx;F=Sq+v_)c{dUP!aAD zc963SC#n(?1YN>#7}fRe?t@Z&J)Nt0Lor(@5vz2O*-C9asAO%Pus4`Mu>CRY6E%`bCUYds zAMD=$@Zing)b`&<_paAAZm5N>(c0U(`+jwBg8ug1h5^4k1lhuJTWLa22(0z7q$m$MwBXNIBv3Ub)s2Udg zn$5MaYym<%!HB~jTJr{M-jKsXrLs5~wpJ&*Cw-B)GZ^KB7Q55pa(hCNh@t^PB*H+E z@Ed+3kK5=Zr?*6$7A7~HP7|^5BP%*mI9^!H5I2sO@n2E}Csu=LKD_b@iUW{v_0mO& z84@-`&*e+N1Dz5lBy6yhvqTgLBqDl426qWB%8B;`co~|IFyiqC z*6fZ2$O@+qo&zRk)!|uldTs8Y(-$Ii1U_SFm0BJly->wYu{u3=m(S+(MHM|5Rh+({ z%NO(n!Z9P`k0w@ZPK(D+?L+DZdBU-|rIo3fnM;>0V?d51{_8i+Vun1M%jN3#`#;8d zjr5FGt2H+E!s!_jGyalJ;0;avN?|yd6HynJg-_&lQ-Kf-=|GX12c07n0WH|Xs>F{` z!#qR=gq{G#2p-NNfmA`*1R$K)D3bPI1zU#2>2wA$PGRq_AfIWWnA(!u|J=Fe-XkK# z2?Y%%41b81s~U_M-ayhH(iEebtqmdxBM?=BXaLb{s=3#D@{!ivZ681F-+ZSuyhF%^ zJCF!O)i^09gQND%XPZwx+I{#|rME@YR-w0B-hHU|Z|J>!+%cK8*~T47N-kkV%h3ibNVa}@8TNivQ8_& z;P$Uro%2g;ORMyF$hw0Cv7nCxq7HA!A5lo#hXS#>JcO_MqH&iWHO23Xs}?WNs05NJ zz+=tsg6-g^N4R-pzCK|5tuqjGlFQ|I#WIcFxIfGWzW{s&Kgi$ut^Qg4Ld_83DVC?w zD9i+zqzY^x#OMVQErfsd)VT^INWhRvhEyq}57rDNm+7yKy?687M7rh-D`6$$^~OA*Xe4exTDZen zL@#&tA1Z}LFrfxw$#AkLe4F*%bpQCqyT9If<7cV%kyhHMb&uZo=nwZl`S-!&U#iW$ zY-5=3@5Qu2Af%Gsp6?yhw(n6Lu)6cGuy;?X58_HzGm68b`&*AcXdb_jEO+CnV!qVP zl)5-gQkCxUo4>mI!JqQI!(_Hn?yP&G>Jr`!yDN~;C{=2dTbXh@o~Z=YoG+rS+Jn?9 z&XnuvQoGpN^u%>XC~8A3AZFd=wYmJToEE3Y;&83n9B`s57RTa>b#8HmkPcXxC9BQi zaGMRgJrs5N!Y~=&gonI3w`bLA=MdDCs8R65bVo4m<#*81>;|_nPGPZ&7bS9h8xQOV zjT{aK495>#;eZ;Za4Ns^=x6yhe)F$z&cBKoJQb(Xd-5Nl2OB1@U7fmi6|}f=`4X-Z zdZcYKQa)IvpB;b-5JD;(X4_1V=VT@>ojtkytkEZ$+fC3CU7s?f&$T6>2&ao_dWbp` zUv3hZ(LH{*w{t%bO^2ddFrxC_jl?yw#6oJuo6N>CO|`MERyvtlS1+}F(Tq2o399w_ z#_jc6Zz1@4m4MLe_kR9wkN)+)-TT=eDfX2lm!#OK_IA`0&#km&jr*5BBR@)Di4vOZ9xYnJ+a1p~R}q zNiD+R;yRNtqm-)+LdjgJ+D?{g`C41g)hK`Nj;I!w-{$oAf-#3TOl4zFSn-4s zRtk~|z*vLoShm<;LhVks)$Lhxxa=O^qQ&Zt#zIQU7mlN6z<6TWtgKmF9-qS#@J5q? zq~T(zroaqeTn{SgRi}4se1a53@gRSNhwaN9B1L3;d>n@3hwM@TG7Vjqzu5}~8&cM! zq?&?@f+0xHQ8gXZ4Ek|~;4~S#a(Vpf6`^g=xOSBpfEWP=4dzo6xp0XZ8zu)T-6g_% z^2*C78YlB>!g(t}L_)~u8fb7;Vs(TW0>hr${``vg67-n=PVnPoRdhW!VVmqeYHi$& z#4--I56^-prg{^4GFKza!WK>i)3w^IcLt9>Om}t&wX);4^oQNvm@}XSwKA1@a@~Uj zHH5eCWOwfs*RN;02c^v;tYO8C`OQ| z^u{n+!sEKHWGksghluv5mhpvEMooZ6AwVRmX2ObLaRs~~#TQGtgxRGm+Z^~;nNd4d zYz~kEqXaiBipUDik(IS&y9>L9V07pZPatS_x>nZK&`2<6?12arOTx&7;;D$1PnA2i zV030-@#@tv9ub~mIej@G`1|(a%pAatbGzN^>+3&EI|a;0zj&Czt>L~PUMI4PU-Uc* z0seT=U>dOts10C-!~>uLWkMo{>_H1bw-EY=2^(hP!WA>q1XV+LOw2QZ0RV=`Jpnn) z(mVn*%t?wk%jc#~F+=S1+$L6}o?#-wXcNz}Y(EDSXI9RgD{tPeuiqq<)EABgVu^^F z@e$%RxL#Yo?TMH4-rn&i|91D6|G9Yt=eSM1Ur#g{jwyIPT@lq0)eK61<{FKIhqddE z)N+SV@<1}}SCX;XM*sd#D%*G3kP|x(V+H)BwP;f3#UC*8m0i$f(O4v zEm^44*AFx0mOrlJ#Vz*t^insR$O9GbKokpwD2x@m&lQL{{oz%+6Nkx~!%2L`vekx( zfz<;)v1YLm12((3VsrUCzK{i1avId)9~hQN$V7bghrx_ZS_FN7PG*)&XEzkK1ur2?Et%z)M8 zw6DGLGMF)%P4EzU%xDsqaXNu9;xeAwZYmQd28cr>sF(*!Trlh5iKaZE?37Chkmt^A zKK{wx55vcB}pNP>b#*h z-}vC>&;GFW_OEL<--d)O_xAIxKHd5HAY18DO)wBOD6rAE{_yTE{_Wwf|J*o!mZ}X? zrFN-z@c5^H%rQVyd)FDyx`N4YR4wb}02W){@AezAWOn95u8?=Ev4(E#1M$G!y#3VuL{H{#j>XVA^pxf>K(6o&g7BeI& zND1TJ4`v|8pV2eqKIEEBO^nY@O^%;>R<9Bedg
2Af9YDRoPX5d44{qku}iphSy z${=FmoMz=eFiFl5IRr$RAR(`TX74CzxTzke+mM$mw(zFh=cj z=lZvw3~xQDtsiK)vZiI!sGiMKQq>`Dj@I6Td~-Wi9u&52S9b3zm0qgUf*S~{6-sV- zgHiMnfoO?CSYSGFwtIM1VGoe7#s-r^b18>(220hkMU{bNi!zBcwFf z+1tMN-r?K7$*&)WE4|9`lXQ1CoGPZ9{Q#k9Hy_7K%{4d9A3arS#xwQm`f<9pPUfvU zsK8__tlA0GAjWff%|ZYN)(4SY7KaC(V}5xJox@_a%~Q{JWsTye(PRpRgL;JSK+tM; zKx|;n5Z(dm5T_B0L|s%kwYvn;xZO6lXJyR_Y?uniNrs%TCcZVEV`XK9Sg#*cxYvt` z8HgeZ1Yw(}`h>_hK$NAPL8t7OA`50rT)R3qHHmsbpa%K`m~o9{wbLjKv<=aD`4UTJ zR$)JZ1PMZjlboWgF}MbhCN=aee<@x_^Vx8qqw7XeBaWBWf{ev@(@-22BcTwN(Gs@yCB^ zA3RRvs#<-x`QRtSbL9K`iA*^diU*@fIEiX!BM%rwGD9rMjTDjKY(fDcAnVG-3m79{28|IgfDM==7%a@v zJTXlb5$gpc2+Tltd7YD=_+2>=%rNy20U^wQ2lH8OL%;!-ATHy?N|HTN^gmsm#SEFT z@_dK}-%*0c=#Y&K)P-U8H-VSG4 zq`If_wNNsn7MhgUDl~V($&xFQ&JPb;*Y7i$TGVJ!8OI;i;;CE+7lUGCo5Rw^LF4FY z{oyY+e)7BZhaW2CZn8GW_4brvOUqSNBOg!d#VT=~RRH@W}XMsyi3~p8_$R5&>?X&+f3VS?$xai(?Zr zCcW`GKeNQ>Z`M?in86dl06{+-Vgt;mR4S2 z3LheILYgGB0UN1INbum95F;!JEdNOMS;|anjU4dXAt_Jaq4zFkH1NBnj^ccEA~5pn2Z!(f=IDbzr6@<--YpIGGo7t;doz?Odcq0vR$|$b zUT*63zSiiM2L~7z8nNufdbU=tZtiZq`O%$U{`<|3{@8o)VeQtljeGC4wr`Tkre#`5 zqte*9`Rvz!?mzk6C;!L)Gr0YXq?mBru=>Kmq|v?pn3!@UlXpi_sls}pycv!dHa7#h zDIR|WmI7?Ri-K7(3shLG%d4wsA*+Cg#XIfVPk#16c}I-SlBgxKI~ zw%hF&Dh`ypfEbZX1~wp?XcSVbIMFRkc|{`-x`41%g!oD|LI#A6mhc&vDiS0FF>1PD zrG+AS3ziZsz?HMeAr%g`O5_moFLVpjWn-!rd?K(R@k6w6x|~kz$#eo3(jGCZLPxuIlk*(jSh584}INVu?@-oMB2< z9S)~2U~zigA%!3bt-y#zIbT>I>VlGfgi-N2_Ysfjiz&rs>+o*5f2dY_>5b#Y?i)(B z>5XOLg(kTzrT$K?)%VAV7SqMagNkC5^?Xauf&^71Q&n?ywNPmv+&Fmer|tW1mUgb| z&7RR76gu0b-Y#WHT|UJZ;FX_@Wvg0#kSPrdf+}sP#TI~tF_P~Lm0aE^ln5z>Xh~-4 z-RGzi<(Nsl_Wz(#uY85q)>whC5Ov! zdGWu5{ftAKuTm;KtmP@`5meK}mjgeGD;DX?rLnNQB2!^ePrq5@Fd^dvHfCmK2n+oo z`5ZL;2XEpYaA$=y!&w3i&%A=~}R6_zL(h$*S7GEjp(ffo1-yd zJ%L`AFA?a4!a+l78z;KR2{C$7T|;7q)Ga=P93#vC5s+^{LaG`l7J~3F%@)zXggJv5 zClG)=B9mhULWn+*$~w|5K82W*-T8O!oF^2DCRO4dRK38s@XHp$oQRzg;;ZVFsFH~! zQ?X=1Pp6fXL9mlA6vgWpN@xHEIbkG;`INjflnliU#21HXNJ+y&2BJxeD@0US9Jgnx z8dCDf%D|{>Qqw1*7nuVLiIOdAg-Se43G=i)n81GT4HFq$E$5+jjhzc|6y=0{sV7$nDJ(ejG6wZ~z z^mMh|ueNtH&2^&B)A>d;QzbuEG3rZeq_z@)VOd&PLECV7FjE3fmn#y9Bvdt?)F?tk zo#O=5umLE>NmKKN!{n}6{b9d~|0s+9D5&WExI)00-R@afTA5#31~%pv*A|x6rf24- zrf08Q8>6fbHx3QTJi-PE74qaP<*I}nK2Clj%Tq`&84Xjz zkjNou6w}*s;-`I{CgtKykYE8B^70AuVIEG~R5B#RabliG<>TBr?DSAH0d5p4t)!kK zRGQ?A1QjZ}2i5i=@eqVPMw1D*59g%U9f*VzDvG@~7_&G7@pPrw*`Zi?Fs_Fco#=;9 zLXT=WZ$b?zhAXJxvk0Ziu}mYP6}?d-tW^^EN+e0GPXk$3QrUE&su%0=Tonu;uN7G* z&n#8x5Vc-zZ}oQW?A(2Sc>jZ)w|;T(@qgKR=Ux{eB*{gSffj%mZQD`(e zqRdx$@2Ie`n=7{|M68sXz*_)XH2g9Z#8d++b9HUa zjf&y+!dTeN30jkqfkbena%DALAST1jKrKn#9gdOB<`2bVS|+A)=8`v>a0H3|@>;Ae z0>dcY0V6WMxVpS-ots-3o0uM-oSK}Tr68Di7riEK!Z(E+Lf0@sD3M5D?thQ-P(Z{N zP|rZtgq{JfaO%jAVtvijGfV;$?>iZ1f(Atu0V<|U%)t7f&5ZP;UQ7|OVfsoWiqLr$ zJZOIgEhJ{pDX~X_he^Bw5hqH<31*yfpwHPLz=1h&{cH!(crK+Da)LdmCMK^cfa7#$ zc?ioZuSFB_Kse$Lg;hPx_(cRpMq&z{2Tvqp)V8Y|Hz~HKq;teH;8~z%S1_jgB8fma z5s45e9-shN#gKOr$e~PHp`toNcyt?r5cUG-)8Uyq-N_)S47t zl7yB{!hUMma5Bx9(0Ow0b9IJZu19D*R3nOm5hTfm1hJGUqRW0E>y^E4<|dgbaE)l|3* zrm&j{#$QA4Fd;)IEp==Wbh+m$#3NQn^1BnM1n85-G6m*Dqar6pv z9D=Kt1Uz7yfEf}VzytIQ5!PkKlw-L7A!0T`Wu$We541UCgdgApoia8I2r*$pm>wo( z2tET=18)lrGzJtMp+lS{Cj=CT`8=>ep8jc&7?^?if;l65Ohhq3h3_fcH$dcTl=Vy@ zzELO*e?-U@)FxqC1aOD3O{#ZxIYex*IAzYCJKX4lEx%r=<(&d@ZRL!m(5^ zobUz0)UNPEG>UWJ{>`;F_a6P^_D8=TKKih{d#k*)m+y3qQaza|WXjYsFZ<&fm_c<> zqdchfc1xwMmaXP-u@ne0nGepQAvp@1>mrW&>4Ezp6|Ah6?8AOZ1&lXip<&~5^C02@hun482oPxqm27^<>f!csR?_f%Nd zJh50TT~cyQt=ML!#tng3*~Ug=a6MBVP)pbu(G&RwOd?4fZeQ3Z!Xc@L7xN(ZdVz9S z(S#O5fhXiCk_@V~*51?IhaV?PJ&QLABT4Z*@Peo-Qb`#pEkR;xSVL0@tN8?JBZYdV z(n%C*dcAx2li$Ao=l^^A(MNiHqq%ih?DTZ5ll;{*>9WyS(lD~MVr`=~ywlx#)adV1 zoUl@9=JMrqp{!<0+4hD}#N}JWi~JIAI&6fgK!9cq#bomX?8K*Wi$5EIlVFMo^*Q=brK z2;z+QJq98GYrBEqM8$_UsKsX`&0$@ydkUNVr0~#ifjQS;Juqu1dU0!~r)FtAw z&nQMARkI^L8%iUb5IFV%s0Zm5{;Whu9#` z%m)ow=?2w|)#c|MY^U&qnpN||=GNok{?mMA!{bZXJP~SPc_MK_$E+^zs?F{4MLZ#j z6K`(ceA3)LN@OZ}u@T9X)BVlf!}kt8`qzUGe$%=2M*r|mqra7*-zb-g4b3Q|ORZ9C zJ6mUPx1a7&r>no0%9PUiax|W7ZEmZjT4VdDvvnh8WJTR$kJsWPrQHQ*qi9*x$N)O{ zkG1gQo{OhGn#z>Y(Px`InXok2ss5n8qR2P$@J6W zo5c#5o}T9YH##Fu1bC2nt@Pk40Wg3Hln>EQvWdxX05j~vCKGRj4;Dd}N`{0AR+%|7N`_g8_#|9Z zZWW?$f(i|3o=Dx|bu)Qxgf|jA*{6;jeN1eZiQwaPLMnf0mQr19>q1ux9vdt4*A_X1qLrkp?_BM|mlsns0 z9K}3|=gNBzpY6Z-@$Ngnx%aRC`~FA&);xaJ-GA5|Y-S2Ilo;SCmM$`oy;>RUJ$OIU z*u;0Ud;hK8!L6i58IB=a2m5y_-EArXF~mHPR~Dy(vFQ^G7^`Q)!Eg|U!{;R`#pm@4 zv{+nRTv%9M#b-2cv;aT=9X8N)~r)=3zP5}GqWV5FvDs9Dw5s+W2R?jX|w+Y zGkWyvGo+bJ=6lJ9f*JTtAcqiBN#y*~!Q)fRFax?EHl&_mS|wt%BY_#GH9!&l=}8#z zvB|M(5)2S}BwlD3*RFyYC>Gcnbh4Gk2wGSj0S^rc9PEHhzkc| zK*U>z6UlTYQ!JG-xl*ChC{=33N<+^U^=!$=l}OD{;4_S>mTRcls!?pHDlhSQffMb5$I7UjWrxJPB#IY0nH%JR8dP&1*E~ZlBHx|bNhJv-n+Ly z`2F_dpYPm%Z+QK7t-X;gG^4Q;&QF*O#!bq1H*u)?f-ostNALdT`ddHkboY8&M|!%P z)UZmlXu@ES*f23BhOVlbmeOO25{)O4$rSX7&F)@YSc0ltT3A?HS#i5v(L^$pDMAOs zg?e#?;42D-2_Gl0jjWRuY7V>I7Pkk53G)Pn$rnpNV3>LXCCaDA#>dHUX5x{##KHM6ilr7uhvl4&VzJ~=(jeEBMM%|-O+*LijF z!mC5*eDCUd;cKN102t5j^EH2>uk-VM=2$RZDDAw7VL~rJQ=(D`2N`IAVZsa>O2{W# z1v$8GBJdOALLh|6ezK2*1i3$uAXg=du$$CZBuJcv4T%{@ybyY> z=9IJmFhm=T=fe3*m&6FA@|+ie5OyU6_XHkHw2)!w!n%1?mOKYVphVe`^GnzOGtQm! zh7;tci?W;WdT#2=_$gK+5-1#QtIZXpwqCJWsJ9ZjUM^RQ#hRMRWb@_jV6#xBAWj|d zDAyZCu3+Ty*-{CjA*N~sI}swfbNr}2ydFz8N}WAw?~$mY2)9VV<&W8j2lGNFQ2k5y zC6ZAi7u5<{rkpD^(o`AGH;e7<(twE3{bF~2|K6MR{o`!8m(JBAF#eL~vRo4#)SkIOl;E<+glA0D{2q&&hNvbJ56p7mH&eatQ z8piU{5@d$MBZ_|#!$trb90m>|GSVod!5{}tw;zVX>kC+2E(eATnQFeUJF2c>URkU} zW6Ult&df2S!88eKBs=5)}l@SeTnd^bvYwSC}WJK0#Zcf@x@o(i$euft^Ap z$I3m$4kom46o*R*mRlu+&?7YrIt6l=32k%=aFY2cC*+E0$N((T3&N!{35ryF1yl?U z7AlBjJ4`xNfFJ$l0gxh{YhDrzj1&Nm08_PRfo@`j?+6Gmb3`U8oi zn$G4Tv3MX53I;=7kIw>qxwz=2)My}pN#eufgZBj8%IC+Bae6VE^Z=#GJ${EbK&kRY zV#@7y3Tb$w3e|QNtqzP2aDo6*m`#X^+4%)D3GS#Eo(-z}8l$<5GnN~$arGKtAp$k7 zp^IF*27WB9SjVU5IAnognr10WY=z^YdIzVQTUxp{Ifj&t8L5Tpn3R3O|7Jws3w zSFeod4Uv{&(jizb95P}rG2dQe zSH)_sP^b&q6wj=l4KflDdG+O1ck3{e#DS7i(iQBEYJaD^eo$=g>h-N#zx>aY&HJ%z zLn-uft=-1f(dLZ@T9E=hJB9K(oQjduU_bo{4e^gE3gHw^hA42l$uJYM=^RnD6k{K7 z{fRguKoygUy1KH8qDyvYFdV^wf}}!D5UW8_8hQo_2V)yVG(DoFO=DlcQV0UWWI6Gt0tw8N{UbR7gaAA+RmgWm3t`E2=K19n3>J>X zI+-D$V`**GM(uMywbDtlWxjlETnwc0`d2hnc$UNAFnlPY#uA<5yNDlLv-}D)xKY@A z5-|97qc{COu^|CN>KXV~#Y_4;Bc+`LQk-p=;(m?d84I&B7$e9x*dRJh@9PK;&?Lkr zGhzb{RCrv?JT}obH*pB3Md(?|nf*sITWL#(Vjqbp5-Ql4RoTN5>6dzjFr7?})r16r z3MU~ZV!Subh7$)9>O^@Gm~rv^#)jP;AXpp=g+NYEh)Py5d?H~bO#yQ1T#+c&+__V1 z^nHGRBplK8bgf#a;4eByJeENIXfS6pxpK9dFO~A;a=BJFa=CQAn9db*#agadf}Rg?v8mBeT?Ek3wFO1~>m!Aue?fPv;Agr>Q?(d|l+I#VLTt7(nRZl*M!~ z*il%6K*Zxf(k1~7>vx}#rh)wN^cBmMa=99h#pCgWVWc4zl|(X5GMB1l^Z81x7FQJJ zSVBo>u~rI&aup9up;$r_No6z1w2>_rGlhbY(!wE}ndHKS-V`mF1vkYiJW(fp9l<(mo&9SV!a)d<5{CYB@Ac` zyur9qaG6r3X9j$s^p4j{GzP&IG7*hzSezqcrrscZC0EDCuV8(QPchuu)Z7yORR9A= ziBLNxCIuxb$P7VuPR`9-o1O$P;6`T_aPm4CN`ZI|4p|@}-{B%Q3|)l4Fm|{$J_fUe zUk?~y8a=b0(>S{)4>jIDqBjUh<0it8f*(vsq=b-vI(TpaTn`t+M0+?KQevFP1IrC% z`E$PJKX(&9GO!_dLQsP-37`}&h-gp~A+DlTNM(W^W@xUt>8V99V|+~RLlBqrvVsTf zNvA{_i5cJoSi-4<7Gio|rLHiNetFtaC#xe!kegUagpm74oRDpnqpkGQ=A+MU;&aW5 z7t{;E@c`oJbKyV(nme2%3_Gr8j9f8aZb$VBIcFFt3UO0CQIwOBN?cASQ3}Gdpc(13 zQ7GhfJr#}Mu7ocYS)gDBouCOUm985oAGlh|m8z!eVrwCf^CFzUk*SkFYq#3bT8vDF z)VD-J(HUtT-;1V@7^bVqY$}DHBdI2#EW?o~PS1#z(M!#UnkKr!9SD#l5=iJayEi~$ zZNl)l{C>YrRa3EOjG#`RKaA@KjRD!WSgl;1fVfzc(m2F(2EBgF6$kNK3^_}BiO0v7 z&r7Rol>J;H4<5#1b(NG<65ff^z{@I}uuCh8E30S&3u|k0%gYEc7$D}^xyx7|gfmY} zpic-Sz}8q;n3*SJYjJMb0&9T*Lip!Zkb*&`#Tc-&z=qH^W~Rp&ubBPk7UmY9K}l{~ zW0gpA94jJ{RL;0mW@e#T`4r4dr2orch6Ie!@-u?POOeD&!sJQCLj?gskWuJGX~q15 z@dK!!m^$c1o_n!a0GYXOBcK8#(>oFa9kk*3iCwIVA! zn#sy4X(WCxiKt<&4V8$5evii+2}Ky!#AdUuS=OkHp=dfR0y+f285GR35(U1-0P#!k z7Pydn;Rr5P>=8miajS~DpBN)%P!=(ri1oFpDF8q?SSdda7_d4ujeix804=DZ0j|J# zu!;f!mJl7vDl?E{8YT-kPy~fKJv%+SgyJ#>qY6zjJ;TQVGhhw&%;`|m0Kn|TI9P&J!d7NXm=W-th_Xpe(3aAVI>H&)pWP68$y7pO0w8b^5;-I`oP30d zPxMFwgp-K2a0rVYj1eG2&f=&GvMnqWX{1z3)$JpIfwI7!kfAnqwjO^>xOSL91NLq; z4<2@}Kh0H!k+>?-!xan>EoG#Q41*d04;fw41T~PtvY<|QwpyxW)0u2KtEnk`ny3-Q zQaPOyd?;Bhil)LYuCA;wN(FBes8C#>Xcok)bGV`?d_vQce&g|bUNs=**HH0r~?ZG zKT0G)r${-)=vZ74)wkj*x`&-XTp>3Yt3cHKWUN>83E=`|l{Vr_MK6gNI9I0u17^ap zi2t6NzA`o@TqQV4@UKoz%JdTYK@QV4F~he&4nTrH(Zv-Y2h8Bu>G}ET`Ni4!CHyH@ zC#EJ?C#;-E4xy|pVwkYMbkYJOvL7M@*f3KEzE%v!-;zaM@K~@C0p0RUu=TuSZ)m#;iTE>UPWNwe7m#Zg@OAYNUgi#e-Xj5=S~ zhQth%HlnAHQ&a%-co+aQPmv}dm`mjFNGJpekyMayC=5WtC}uJWDg!TY7>%SB3rB;#K)F!DQnM zz;fE$*fAIrIhu?Z7Ki0`Rwf~s+4jCViY zREj1LSuu`?0|0nTSR(ph1HIxA0zxJx*_j>iuhK6*ZgmX}20KU~5bDL;5)i`qsTjuQ zQLY>Na~V?AV2K5U9fUuHXI2W?*Az7lm(v$YC-OZvhnHstS{Z-`p21@f1)7qH_Xjy|EO(b9|Vrc`d0^)!gfYGe3<-n6nS~oH= z%4OOdFbP82Ffynl`COq|so@||1ivY;Cg3ADJpg8C8mwtjFrmSK!)6Tz{7@RPXv9bH zJQM+&Egp?B01hr0RD?pg*P3AADsQjl<{h1bu!b7UwDU`+P8} zXd9S6Fsejnfn~apW(cG>z?Uh)a{w8zq8Kp|Mt@=~XU0oInOI&%SUD%0Df$FSswf%4 zm;j=L&HKrlPP*bcFHM%S}Bn9~e22MP5F zLd=a!Ay+6?YxrI}!;S7>n9b!>C4q_y;SdRuCm%}dsdzLJWZdLP*yC~vF4DRd_W2T# zpw}gc24b$_s)o0=TJO|beQFD5ilWZ2)8$>5pJxU(gtmamfPJ#E43~jbgPMZI3yCIQ zb?Gc4H@335vd0^M}Um`z}0c5!)p7LUpNngVNYi8hBM2PpqGG}aH+f` z(2dE6{l=x_%bXA?LUg{&p5NVlBvP2=?p~s7jYtqR%UJQVqhtucFvBSjcL;5;0q_7b z&<#Y-_!x4J0m4$E0;lzoc*6 zj8%qYb(J=6Rd#Oey!CPW)>FNL70w&s^*Gp+S@p|zIWqRtJhEG2`kTuF+nhG!eL}OmqkC(BG45` zFVUD5xXm@717`+4Q0x@=Pt#jVXh$`j$umQuc)bB6Wtsd_Ui}p7wb}4=;EThX;q`jZ zOEj|Oi^WJRrX?|yl4P=HOSN>aWMMdXyOVHLP|b?-$LnWk5-3zahl9Zoe0tywOSF?U zD{1;}zRsFm41hu$m(3d_!i|!nVoZ6L5B!*4S%sBE6&RbugDPwg$zI^#5bP)3cG8_; z&9PR-CWM!Snma;&Sw%$<5Hk-~0jcU*T(goFFOD4(Ic>O32Mll3l_2#R+ecm z&Wj$28FP~pG&oSou0S1A$v`O~;v5{pjRKH}kx-bWJz}~`&^g!yY{(dKwqmwO%wP!^ z2ovK};h+%dmCIr)J)@;;&|^{;;D^}3#0g>Gm`)SI;)QjBMuM_&;e7wWdkhOw*}m1k z_u=}z_bSJ4b?&~CE)OZY8A|7D!8pZudz%N<=6a{U+v;tXnnN70-R+yte)I2d{{Fum zKK&q5ZN~`A&X&*%%Jug8)_!NWzH@jT{=HDCU=!q7qN8Y}&nL6&S1K905emx(E>ciUzyLFV8yls;mliQ8kaz=UOR^;ds+Eh-bIvv+l#!YFtOf zayyZs5ods4A&!W+$W}$-O`?+q7AaD`h=RH>zrw46xEMHX(gdi&glFAMIkBK7^X`K{ z;|WA@o}L0QSTX|@oE8{B&tMhIm^U#4!v|oHn88PjE0K^ujnS)D;7lbp&?zsB)w4aa z-^7d)v@n6>Q}7U&VKSidv>06ET8ebblhTC3NC7Z_jdSM?-u_wh=#5Bav;W|e=JhA~ z#=Y%#{;>DtUrJjKjm~De(kL|gt-)@!-eZXNN@tzi`9gcWzk7=z&2E42yUm+V(&e^} zm%p>Mv44B(;COv=w^D2N);CM_I^kPDNvqc*j}5H>w`#RkuT^V;FJm-R2oMuCoNyR$ z3|1>e^(f)ND<293iwE?;qR~_pq+whU@gqZkl2x|4iVFqYR@7{Rhr^CH_t&M$(YSah2B`V`o z8`Va;RBP8-{m%M!wbg6&*Xy000FMIx)gpF-8TOSxrEuWau-gJeb9%if4c0Xrr*1OH zU_SMf8Vv_=e=z%qs_=LK2-FKaFJ3o1DfP7MkP(be1l|BVAs@q;h_M7vA}Hg9S|KzH zf0*D<5f~PYN1{q79N~a)GyxMzg$&dP5@c915(6be@FJ5Y&rB*CDKj)6ZovsK6UO8*5pgN#Fdx_| zq_9rSlZ!SjlokB0b3oAwXarHQu)GEzO4^zrTg~_n&;mUJ*f4QIkRrg3uym$ou8dE^ zzAQQ1V^edyeK58KVEl*~XXI1@107P65L-Z2R!p4&Wdf*p-DES79zhA5VuLALLc)Z> zz>g~w3n4~=)BsvI(xgL)&OP&KU#iI`Pfkp%;qzxK9TPS{3u&Z)9~d=YhMeI!q;&&+ z5H|`!2qshry@?s-Dm@~k6a)7(27QDU5)V#xJ~dT@*=%Bf00XlP5`ZO`H-Lq}KNH@B?!SuUZ)M{~ z|N3{jSHIgjc(=226Jg%q*5uSrT`fH&ail1 z04{L~_(3HFRB*w{T3ulS8E_Jvyhvb>Z&-ozE$>MOc+o4S-v#f%x#CzRBRanbB^8iB zF`Odmg3MWoQuLux3QoXCXEF;7@1ZEt$IM{~ATSh&X{etgB920N0O@3SUkMF=C|4>+ zHF%{df*P;K>u`|cKvm2~vn!q^)mn;;L>S^!BAWr}284e3Wl3Wq!E+wnU|QD1XAtH$ zVT%=6C`SR)mvRaI$ow*tamkBVDm;?iBw4etEQ)|a-8e4=-nn`4Fwo|(@~0}Lp>nL8 zjSa!4>~;Xzw=-5ef_CF;s+`4;8(+w9&@L+lNX+W*vb)e zNlb?j-ug%=;aInV8f&eDda`F8i@4orL=Iw7}+gs^suSy7$gB;fEj5nHt%|f-;Xb&pQPNmr` z)tZHTi4L|*F0ZIxgd0(~TM?`=0*8XA4%lE#l);9;?)=>Hf>1bdCh#Vx@v8*oqWB@S zfjF5;MAQ1h((3#iu1479^~3*WP~yS@#SOm*<+yPtKm{KJPsY8W>c@C9m(xev8Il<2 z_5~0L^@d=iJNN?qF0mw$i;qrLI3BdjleEoJ28vq3Ly!!(Bp4)w(oCU5x`paYu~dMY z)PbJVXpo)(kwW1xjkHu`2^^@l2%G#Eq;Z<#iDA#NpF9Krz#1m$+4HkjH;VMNf(L>M ze20)^c9E0@vKnWmiF9O46W_&u0WFA0*yg3M2c*H(kYES2p=^7e7f&tWkx(*w`l)Bs z-}-0x%YNj)ijnh6pe~9(V9~p!UE{7aCVq4aDP+Yy zdt1GoIO=!XROjJ4T#+mYk4j$4B=pZA!*Y_>G`X!aWJaXcD(;1ZDw5tH$)TJN%Z@Dv z$N~m|5LNg9KiI&~k75>aN{)OHzWefv*gcT&A=y(!D6Byd!3+yQtpg5ACl*>9ZY#Wb z;)%-e!rG-f$XDcRy<%g;EJeLg%T*eP@%kd#sxMY)?=*)O3Y~GWzt-B=9c~@e`g^VM zetop77n{Y}pw<}YimhsMn6I>;K|t87)w=a&9~8+`xyTnX^thV2sF-AwiUwu}DGYKK z2r*4Q*&@e*89cqo3kfhtJc8{z$!(yT++<1c5kwGd)s5ii0?c!N82MtiH{^5$mK>;wBYJKi&4UQ2 z7fyN%?GK@>2BQ&R!0RWu0%ABFRED(#glU@ty>2av0;#suM_>s}9N-EhO3As*&JoV2 zP(rcEA$(xv)NJ8#Vezm^NnlLRF3gbII6sS!7kB`IkUS+KAvOUpU~4cig=!1zR4@iz z4#}BXL{umv=Vlk_bt9IUURb8;gPXz#CK(v}tgxrBfJr0x-Tu5E%A+T3S8WS6SPO3j zBnTV?8f1_FIXhWfXu$^f@sRvX+^YQR02^n{5SWvTrwmJZ1afOh4&yJ0T@_-q1L=5q?(LGuy1vwS}Tu1eUUY9$)bW2$DVusrRT)80y21OLkJt} zgv@0fCiF1tJoqjzjmM47j;0q2#b&A2%~ZS0Bg)|)bOuat_Qy10>u1D4LaDlmTe3IS~@`!Ypq2xBU`Fw^5tx?M5|gnnWC{R zlamA`Q3*v?8pLKTVLdzy28u{_Vrgk<5#lE5-e`BqLjNhC0P{k`zkU8}wLwug9M_`c zHFbl=cKT3)*myDpk&|cz0Z&2$im1G3B*7h7Vu}DoO*-LeS_wp`xC;M!84+=>(;Z+h z8+abRa6Bjx3MbYh@dT{axPfv|49uWIR@@9XU2xu1CIc$`);$B=A=c z#TgeuJa4l65RgQ|PGsF#l#M<-KMMDwZV49$Egmp~8VZ;}_CjSs&){erJ4TQ~PV_W7 z3Ro@m6pI3)p7$8BO1&sl08X&%59=vMNn7=)LJRdpwXHA%VBiG}6kH{f0VHrv)_lvt zmBU!(D90n>mE&<`w2i7}wab99QGNSheDzLy<3h69D)q+2RzDa?TzU7$Z~owqk;bZz zcQ&rvSi5|^y|dq5->VFEhF4zSy8X@mSsZV zv`I%gDT(=6)NZB;KrlsG-{>;M@uwSv<}=U&y_;1*3^6zmgw;yS63p0ox&;@r37o3x@#;;(_Am>mtK|I0)IObK$?12@T)6B%UIS|U@ z2R86x@Pa4>L+>D^!7$2L(!f(e6_n}Mpb@24SZ()%8MZH?Hk2bmF9yxt>=_P+Od+8a z4D945;0GnMOM(^GL0F_xm^~?yfrJ|pk{sd(7$Ef;T3zrt=uDv(L9|Dql~S*no|)Y{5t;Adp(oouZGM?99yw|ZNH?aR!6M=-BE+(}is5u>pG z&X4<--%VE53jN*A!L6;=-rsrSn@l*XZd@H)e(T_k?_PfAhugQ_?`&UXHW3W(biR@+ zmwA!*41_{-oKhyG#(|Z4=%|OgwY(zbRHu`qrrSg7D7`5nzXvd&<;5WORhO51B|w1! zA2k#WeJ#{O-EPvO{FwkP-VlC+pR6dJgfz1=jeQQ?i$%KEaZTW1fHDLnD)^Z&QNV8t4se2+zbL2oCzEb+ziQNSe+)4$F}5q z*dxJ~!-W_!oDxPe!TFe9S*0!`+)}GWmah}qAS3$#|Jd(66@UBeRO`u4C-+QlPabpE zHY_zzfrT0w%>ab4ESXG(Ur~*Sw~IRL4!<|tI0eT1Ci#_;nCARA7A*K2paq$K7B;LY zKldddaMzvupS|VIS-?dyc>x4xEpoZV2ZIuJ^8D!&=TF1$dI?^aWIJGiAXg3N6!Mtm zSbzj7U|~Z+$j2cEP{A>*6#(F8DAuRACL}{&l6E>OL>!=AV1^AFw)+CiJn@8|F7|TG z6*toLe!A366+28rVhEC1V(3YuI@ldwf48!BHQU*(ZC)Q;e|PiFx5n4sTfg;<-M7Af z^@HErx$|D3JBD4JDpZ(Z7Dt6gr$mybArjZY@1u+=y^+EIjYbjM@O!;9uP!ZO(yKuv zx^zS0f>4ylzZ53ie(D|g0g;4(rmx5A$(Sj+>*LT!{XSij-dFgWq$^-r(5L41QT_;V z{QQ!GFIr#_05GVLf*hRC7l~1H^-~?ml#``qJY8fU3lM^OaRe#BxPd@fAgYHGNhi6B zfS(x#bjJrtXk?jq9rcEpFc(I8oCK(m&5iCOHu(g=xa%W@K(-@7J17Sb&UAZyNw0R= z-EOB?EFwUH9tjB&ewkJPw@3&F9acmEEU9Pm?##7^N5W`G=xaD7Fvu)VMcP$(;jHr~ zsUj5X5A?o*6?G(Qv7RGT_+LBk!>q7i7CZnJlEO?HpfpWEiDV7Q9{x4?!yto9&tl73 zdc7XM0TzB0U;_xDK!V?r!il?nhrHd~+Mt(Og&~*&G-`E40E3HyBK+*uPjFv9^?m$I z5BQM7-!Qq2r=Nlo9rSsHF=|C9AjgyhCe&SDfFerF!VAl`8kvNT4hGnmfuXCn_L@By zJ!|EHC=5_Aptdb1!^)+SUA5*V07VLc_?G%QM{+{8K^k5bX1GJlDQ=bqJ2)2cY&C|Q zZ>ec!>q)a#X|LxS{Ze z_pDGm@dasErG-C`(EKr-AF9{}*y>DeVzxACQ>50(_J;M0JHW6snJiBo70FYWAgG&C zF=l+No2J`%BFq^gaaRys(9CEof6EVWQErC<4^FGziWkDxI?)WTL%|8Pp-cypYBZX) zdZSuz@LQ)SERK58h{iQZ=SUAt2-giC4lPt(WLfdmmGyjFWRF2XZQ{iS0AZy7AUg4P zEH9Cvl9VEBB{fRw!zRDc;QL@N*2#vksP97_?#?}5_bK`nr<65%N#3_&K>2S>Ohjk5Jg_((& zxpH@7^TxZa{acOgt9!TJ-M;)*Wq4)t&F|g$`LAz$`$w1F{!XUR=Lr|plJu-myCh5p zGctLpuhI@nTP&#pybb6T%w88SVTnP9t9lZ^5cM3gnJXkNLbT5oVVd{0_Q;|#YGf62 z7R;deK2FO!E{he9&=s;Q^HJ%B7k|aUOode!71sbE1StyfIjF1pLO3g713h(=2CIo| zmB}fdSjxjt*{BXwAX$ufvBw`L2Z~m(i%hH2!6jlTQ%e_ok+?)a5gEV8gp^P+lPQ(& z`fL57-jay54NRd3k7O21YDB_BErS979a--&Rvdet?iB#H+(0^PHY6gW37X^>(Dz(fS;WO)P118|iDVzbSt5&r zw1vaVv_xVPCa18TL5i1vCzaFlrBtbrFw3F1x#9^edcvMW)*VhQta_LY5Z1H)kj{j9 z#ySz^K(KK8V0gtqvX~X2K+z!0N{^aes%I;W2kmA&coV{aCKrr}x5{wU^WEN_v}Qiy zt->KTuzEBIC`s*Xi;C}&V{n*4u)k>I*5rn|`h)+$KK_jAiu?qfc8kAy$lAaL%g1?C zpS#IFhznqv=g&$Slwd?$4U{>o1O_<_z(aIJ(Fi?ghYTw zvPTvY5=VHzZ;8x=g$9{EB3l7OKN4$_HpKJ^^n3h;=ul2Sr zWoiSO-;K(U4YSlP*89a;k5C2ff@Y*4UN9Z4Io{uW>w|;u{9^BepKrhY-NCiDj8Z!k zP9S?#Y7MKS?R2R@Yyve4oUqCqjv2@*Gc)Y;P!=FTNg@>R(30s&lN4kkUvKSR(+bVy zfEG&^aSq_C5BF~*a~1NWGMyw8p+F#MK6=zF-WTj`G9#iAytss`0WAvA9QxeJNeC&N z?8vH%u?OyOJP=E2#b(f~ta>Ah2y;;gNtc{~gu@f!;2_S2KehsWBOJGKrXUOMPL13J z>tIZyf)fnO6o*Kr#Pmpf{onEL_|ZGu@=G=W3<5xc58gg1KLfW4Y|s%6W?&)IO-N>d z8ef3DKcGvJH#&3jB&0`LTfq-J53qp(EO`#`HcrvVMDw(WgAuK;W+(256_wyyP+^lr zm8n@%c?Li%NRW~e8#p2=gi(G8+iX~{aR?Fs2|XE*2!{4=2_xT;G5FTf>|0_piJ?xbWKc?QbTE^ulKu z^6rPx)fzQ>V`^k@FqW2C9OOY0N~UOw;aPcx`n1TiM>O z(HNyF9lg+KZ(e6utI2Rz$&0M|AcE6VYSFSuw9)G!&1#G=5q<}g&s+?2KRSaa$XnvhiIRw*JT50B zP|CJgj71KAG*TKL32t=4k(Wef15h|~>J`L9(JhCxapvSP8rk4|0UP3N5V?>Wwd8vU zWlk{?<0LFnX15`hAUZ2*p>(DAT3EkA3k3setssQ8dU0An=p^$s4sl4TCpa809OH2g zsY0h=q_bvJ+Cd|yl^R-YSRCzDN1I+Az&;-np3BWHv)~z9#7Lv={`E`m|77#E@9o_A zVE46eB}JG#3CVlKuCk-7;pHJqh z_06mO%Wvw1_TbX(Y=5URK4`CB-MIQzt~p@BG)#Pa4XgRgJ41UKsZTWh=4m|f(2mAQ zr28}w05+CaoX|o^#lq2Y2>k-?IOA2_o_M-2+)GwEfvCZ3xLL>{3{y+ymfV0`K4_GK z@ibHnSmwxMAVZ9+5lArIEyCCrn%Ho2Jb|z`oWSjP9Q+K-5!Yfc9PrG>1V7R`ALgCh zGs#~nIFyQx9eYV}$E`BZqk$avx+vT?wol&?o`j;3pFK%R<2Wys6(!{$x?WbmA}J8m zJBVn`o`D`FWDb-VNSjJ0i%1#cxX|lH@F?)2bv4R)Gqm+Hd{@pLsv7b>7q>$Hd4`D!#wY!Tn;h;{i&$uEi5eS z>8dvbH#6?`hjWb{&J8nKGtE(L{bGIXVy>~icIowUca2n}i!qA+0L2U92R0yR;WLXw z9}IIUsv@S7f@n$z;R!~w!n4w_fi6?TcZgYLN zHr&dh#I<#0VT_ofdAxI!$Ly`kVMZP}lY~75f>*1Ig(al7* zOpTXeX{GkMS?=Z<<4kkhrGcf|Wr_h2jqpRAE?xxLWF45N$_tJsqT*IV3d2;g42q?` zQ*88zcM6SOsnHH&^zm#e4=rsc-`&o&#+gcoDNlqSfkb-Q?I+ujWJp`NwFDc~3$Y84 zQ;i%64$HF36Eo7xYb84Z)szAa$HV$4RmzXzqxW8``V#L^2^fmK>2#W4o{0e`jvprv zNLG|M<&jKpSeOBFkhR2_AlC^19NN8=zyK0>;VUS=0sjExQ0Wa-XHj_$)|R&+GZNI- z#ka6X9yVYo4@1QuDne1)BsuJI%y^t{ce6C5^~T+;gZgMsFLtwyQG0x`(p%5gyZPp* z(%Bhp-z>McLh)RLIj+H2AeKsHYK%aMrmG<>?Fqz56)>-mz=7l@Lrvnz40#B0pfsLh z<+%L`&E!E&v4dIj-Mt&_?VIW9Fp;h|H!d`0c{L=Q(;;VxEJ#de$XtE$a70>9HWSqPSppIWG0bJ8-+?R9HR%0lhe~i zFvXckvM#T>8S0As#_W=7Vbu+%@hi-D$T5S;mMU%$p%k)NEQx`4FTe6K9j~;#(tbjV z>%BRHM`DjvIYxuT!>RF5kb5S zX<%VG6Ga&|l<>L554EZ)R>zLiXmK)x_Q4B(Z9Gm_2C;ON)P<32rHlRE=JjIl0?vGK ze4)H{sk3tnn=Zl?Z9|@@GW!{FR4QAqbT-nxjqa6Ki@h!Qn>_E~XvzG5rIq=`6{r+TR=J?Q|sn?+nxOzq$nXl7&#)8YQ8;gZC}l`HdE!E zo-V;a##w=h4%GuR5>5ui)rCbI4qntftR6Ks{4luC<$`xk+Xw^V^-Ni~pX6io3{z3U z5nZTRaLF@8Z`=$fGL&AqE&^rAT+uMIW}(6XI57k}!B_&2pkIUR>#~zs@NRnGDBiBP zeGI<;3Ns!R%)rmsSX=XXJusSJQo?AGKX}x_pHb2}j|5EoubWhup>}~B*fH=R!G-k8 zpTO;a<;4rvf#Bs($lTI2rjnwvoJxWRS1aqqLnA46CdOd zejK`}7Hkk4@$#;9FTU2l`etkYcK^y7#q~?I-RqgbPNuh(8yvLv-i937*tt>LAXM3^ z4)#VDZWX$lrS3LODCrg|qC5HCj!|gR9f|`$y3@*7EYQG#w}JlwKa1zN+ZV>*RuIGp z(l!!d4l4OXcUUtUz0tw-Kr|JOrPIZRZo z6;}q4kOr0h_oXE&DgX(thC3L83W^T`V>}u+ICsJBvZj{ z@L&a4R4Kqb}-xpy5vJ{G+*7G z`V9zXY;062RqRRlj0{GEwLyF$(p<9qFME76-_QU;5CKl8L`IER7`lW zlUl8mRIC;^QWR>i0r^d8C@MQbsIoA9ex9$Nl6fl}dG>@=v!zliqz(KGNeY1>Pdov6 zytQ|uvVOU|b}`)=!wydrDrTiw?(b$hJ3cMrPZkL!+B+B9d)Iphx6Hb3zLp zG`btj-UimmNOgKl1UDAxNf*`@RSi;$zzGCQp4N1SuDGCT25|-8YU|ljs!-3@y51lX zzNvUJ%LBgL9XGeG_4aNR$%htd%$$cQjwBzsRJvFJ3`G24a3L$h-^K?)sL{K@m+ ze1u3FpNwUA9#n&X0N5MI-~%2Y1m;ea75N@f@Hsy<%{-hbt6Xx1{byi^fgd<1xL+_o zNN*@tgKrU@02_-)-JTI;Qo)ilr%?i$J}c23m_b$pyjdi%hmao>gueA2Ar5mGWcemCUn&~1fA%%P+m8-de@#%S~Wk{`r z)Ti`rK=%y015t4@oL=}+FeMnZ4w)POBS2?LxmIcRB5?ypBofEfpert#ZS=Z(w|Zb$ zZk?N3QidBXMVEE$9xwG@L zQ*+ZI7p8_NAa1-cbDq(Ej3HLp)nd54c`25mnvmgTmOC-Ch`hgx4rzRMxyezULp_2DY$#4S)Zq_NDW&1v6@bcx?zk61CzX$gW+tNnEr#bD7KE> zL(u>U<_J!N+$AmrLpIJ3cUaj^qBa~3vZ5dd4hP3eW86`1oZ@rff3TmkaULoxBul)G zd}lY1$a#YaUz8-mR<^b74r=bOneA;P+oM8f)2Q{eO24>v_3{V5x&GsS+LNSvv$Eq(fzf7i5p8c?)Q08!WB7g^z00<4BYtu3xD*;OtFYZQ$ z){<;AQ{chxv7jZANOm^%#}{AAaz!+?B5^6H3TA|<(-NEDXP{a~b`pu*bJKH#Io?oo z34(ZtmJrkF3sZLmnV3<4k}18oLEU7)n^9~g%Uv!Z0vl9fl0++-RAL)_`r%OTqXW$> zl+%SO!aQL_fU@OkZ#Ui8M1-i&+b$1wLq^dkwVLCdI+LEhf{aHEi%=k;Z66^URX+qX zB(e9bj6Wi)VLz(xGQmgdtxAvb;yImGaR$_4-vBLg|U*jlI9s}<_7K~sfh z9PP4FXY1OX;pWbxW-y=Te$n(n6=N_M{MIjX3`Z(iEp;&4>|}w!t=4y)g#CO)eaZoo z-{mbIvDiE9JshvjqrT2D_MXZ8>}0w3Q&c_Tv8Zw~UZ8aH)RWJC?U^SZ*`n*m;Tf1g zss=11H~Rc@Uq5DzFM-&0ifKE?g@-{@LWXqaJb6+HPkZ9$!{`JP@jmFk(qqeRU`jK?o zY;5V3Q7BV$MU4|@nWiz#xbxHJrWYNaxKYSgx^X>^v*K`(W(WnsNira$AZam$7DdK_ zj&g_BmnpUrsTw42X*I8p%-YZu*6FG()Q2?PCz*S2aI1H4$3XZim4`VYsSJ;2adGL) zdFBsU1I(CVcAmMW=TOb1mjvI!7tx)*2znTF!PA;XRN@MRoq@0$&A41$s|*Xn1B6Wx z^P`mGM@=rCh$29kChtkHrdsQ5RNG zHP**p{-O!?O5X6=wQE+e6y#7BL~v}<3L%}N64h)r z(H0{*#t`jKasDADqdrGpguV0O*}&UilnakboxDzHneOaU2=G- zFSyAxD#<0C} zt-p7(P@x|#7lk_N^@ACxhs#)Xdf_b(V`_SaG$>u_com=put7zGlnIFwimnb{fIJ7u z6>=@fO3!Gl>E(V%&x0sn20d+lMl+h}n2|9{jmB`hG2BgLN+CTRH7mLPp4k}3jZ(Tk z%nvR=eJzjoHg3LG-nypK#M0_+u6_A21GC0J|LU*)>QDdlPi-);jo#kCUNOYq|NY

({RX16-Zk;7E4z5eH!R-~8q``QpF)%fGO} zw-k_Y+CTcEKl+D%_y;~>;Rd+w?c2B2b-7QD1gL)V8@cqq|NFmlkppY_pa1!v|Nig) z=CsQ0F)*(3>tFx+{~axV_jiBCdC)Lb2LK^l`oI3`zkdAVA3r=$F?lBvo>&v9iN8Py z3qSCwWK1;>V!;M+ne3tL%=l}uDJ;(!LW=_V3K~io9U>75ISeUbMB< zvL?&P$RolcFQkxYlD;=B55c`clRoudHv&2goR23WdXh9~r86$UHEFLI#dTH@a8eEY!NqesBC3BtS7Q9^rrXXMgs`fBeV46*GSC z_kIt%$6dfWF-_iLQosD=FEL_F?MFZQ5wP*AU;PRv0K3F$Ihf^x4?e(kc;k&XFm#NV z!xR*OH`qO2!OKBn9CzVw{^oBu9>c|scXxN$`TqOw^A=QKgNw4@TCj&N{`Ft~HTTTI zw|L7JUw!peejNKJVkckZfFJ$nhaAK4thr&d50%+*8z2_9^qb%O=6m1!-Yiav3@I#I8>~SKg%fZrfC_sppz<~FgMrra z5}2{4*IK1%6njK;=lQQQVHCFmM0w#@eQ;s4e`m0FyEZ-`UPzZZI2$peP-u+nYX=ZB znPA{g7Dn+iJg9HoEcN!QYgf|ktyHCz$~6kDjmE~Ma-mN0bL}xmfZ7&@ zZQVa+;1&Q0SQvnT-+(D|9@adM@e;5(90ESVYTtV6E%1X+`3imsV1OM1A-tW$4D49J z17^(vDgY#%PUkMnAkz8Ezx+!+1po+$xFQ!K>ftS?RY=2`zy0lRb8qYc2EaDDt8s<*j6gMbk?X#Jt!8qVP3}(_jX`p}N+dc%nMKRA0N)-?=u}y;kjS z*GC7<^-EZ@R%lfE8%D84$%8TFm?jiz=5>)vpwAO-1cnP{Feqepfw3CynI-4!vJ39h z@``)e-H2Z7Y+ljwEk+e-NmEN^MFnhp(AhkQ zn>omsa4BF{(6j>I0!jv>31Nd$bfwc|Y1zS;LS#Ux&OjAgbkIEFm&OwzZQw~H$Z(Km z4W&w2WfaRcy|J_}tU*@iQUpv<1dWYUx>%2!r9`?2U}*U|m|<3W@j|Caqs{nIv44;z zs3}!~$#kK=)jPN`=!+=$;{Y0;8Z&tG5=Y=+V7SU};8X-AgcUsR2?el7j2IkZzd8VG zChfsv`&-}o7CUXsAR~fHf!Ctk1u%mRQXqHv8DP)rufGlwu<%{Zra~2kRm!X20CoZu zyah-&GtwXk4lm5ly?W=473KU3Py`;-YBi4GW}tM~egnA;qGRG>WUd~5%)rTzH0aY3 zgQ%AR1Bp{44&i@z7y=J!{T$ARAc>?v*s1&y0SO6PEZ2ndd|meQ0yFS4SU>q>^Wb)} zG0brVa>H9zSCYr_RmH(v0DeVkS5MUCQZ+ z&MkXqmfW)|?ggiNW_gtWB$6(}epr>sBTo3-)HjfyK$8|S1li9-rjn`j+q*aYdKPaY zl`R=*rkE6~-L*n%kSvr#35|}Gn3jZtL4%2Omd-9P8i*>Z8kTJ7{LLXTj-P=Z7qbc& zh_*;)nLFSmP)ekIdM;6D1(OBJq_Iqyc_p;UQ6&L&0^u-X9uQsm0nKtdRj(JDBc{4! z8pBL;JzZ!bQ&t-6rCS^1WFuNCS#FQ^uaDO^C6zJy@;e%nn1S_Rf9z2U#;0sueiK$c zV=^`Y8+b^oIA!uMzN-QgERy4~Z2MC_B6WeqA)o8gKe8Ici56t;072ynP4Cuh#m-tI0A1b&9X3{Z_!2Nxn} z;$}F8g$)Ho46SkH%FT4-{gBNquXg({{Y52sNF~!;iNeLs0`Eb6*gy215u!MhT)Ciox(X=`leSG#H61%wW_sM6Xn-*`!~0 zu%GD+bA%zaah1@lx#14#$y%>8+9z{L4Mj7uiL6KtAsa`-leijR_63tt`HVmLlRqIG z00T&IszsGEIDp^5$6*HfQ`^r_hjAokN92Olf(qEZ{VU{DJoxX$3@%Cy1*pL8zx&M{s z)B$8f!K8;DGw?E0y3?|I5JQp^SUi|PqVu^44}+b+!}FFi!G>&Gd(;Ns;-$6~%*aWs zzE)g~@i>+$l6457ZX=R-0kks3!k~1=bVyc#Fgn~xn4wU^(VRHX2=b|OD0YXTKN)nQ zb|PlkAJ3wEU4r%6`896#XpihLuF&qK_Lpsx$U;K;AcQJg*Qf2BJ9pk zj0;iJ4JY&IfQj+V~Hj+^t_W8g#a4SG^%7~(u~rNR_*1wo3*uz#ol^xc#y1) z&Dwwga`gA+yBqo57NHGele~c#Xj-V$tpP<}e!#$*6=nb!n9f}brir6~XMlCyiy128 zN#fGBU4;!Ci9-PV@O4re3P{vP9I1?(>Ir*nKLgAF12B4SfCGqJ*dVfCKZcH1!CSxs zyC-0=+>F1&;jsM-PD@nP_2RUQ5}1G)QzBXAJU6=p`ymQdQ>RS21_KJU zD;Gj~(Ihp}6<_%L;>y(AB797y0nRaX7{jNpm1Jc&9!(dswO)1YQf=)DgH+O$2Ki7@ zCa9I?>wSY6j`@PeA40az7YxF|pnC+sfHF>(J=GIngRV6I2$fJWC5$KlkeFnm+RIqe zI*Xb(;Rh)V93BW3;Gfgau(%AAPcVZP6bNJa_8OQGHp>w`+ugri=*=nDNCXgye zWW-uFl*o9)FwHUyLEK=h8GqGlA3Epiul~!oR+xc#{l#DW1sOnW5!+&I*AcKG1six> zc5H&Z@+9WjtZbK)02S;cz47y({~W*|jX{8*>>gX^Q;xxWfB3^6@-0FMwiRYz+&CXx zj*UP6^FIeCD8T>{U<3J6u3}>b=f?q3o`Ie2WG4X#Z}|$h%Bk!#vj+&_>Kuc^L8!v1 zY|LN;B&f#CaL;x=1E>H<@IRPT_mKM;Br{;cTYiR>!6a893CzR%Ccpp^JZrIE6$Y@j zk%NfC#*gP1c`G>%P6Ffz(x~Ir0UY_t391iF4H45^b;xk>kc=H6V}TY0DaA1T7YsBc zkGbjwHcp+z-*8OL&d#iQ^kPfPwNd%=>v=t255!GIuQ5v56^gBTgR_eci$4+eM6@Zg zm2|Dq6%x^@VzhU!R@N^;&W5$kypa`3vY|+&vu1L_G*woO`2;BJpw9(y-eu|Fpq4?) z3EE$1jf-HH$AhjP7$#nb14N+mV&YZM@dn6=tau{D$?=eIT%uOlM0dKe^f)}ifEax}A@eSaC!|vRQ2@gl%s6ubvy;cT%u;vD z#KH)@8wU2h>h@B=BsM{lo4f*_l4P8kMlqZ!>(bncTg%kqW_8sQUT`yMEamdY035Ws z2~S9J&;a5HYJM#j&(x+Com5iL8Kx~IY*uSK*XiSkq{{TfGx%tQQYp%ut#Pi{G?2US zp%xrY#I?nR<+-_es$M_@6G+KMpexFVMkh?KP{bF3nC`>RU=KM^UpNsWx~ViNn$Tnh z3l;E#gdxrEMm7{ppfX71Mc0#hx`1||+1@mp>)F=OEcN=(P`bMjqo|?bQ9-w(PA-SG zIsBn~cat8I!`$a%kO0L%F|v>2XYdh7Kz4x&2*+TrUK?<$_pW@Ac5LiE#R+z!JKqfB1nTc5d8!pP(IG8p52e8CP#3t&Z zSUm0ykifz*ENYvx@kMoMAWA_CU*uEn0TALQITdd~8m7(?{WAlDY%H*W^TC=7DGLb1 zgG+fH4{dB>IT=qu8>eB{b~PYgJPjLETn$NAeC^q%E65nM$v@w%;RzPLY*A>BnQi5I+tx zus?hYVht5ya3q-q>{a0hzaI7wEMV4zA%qni35qDIRiE;8`~X}HpaS=TBk?(uyTAc9 zW?fspd20IBMcp{h0Aeu8#B~tfdnqZ*SRjI{pnACN_@e!xHJc_s5@8g1z{IZ z$9>}Ea859TpMo+9yl|E?XHNGAJ+KOBe{y^Az3^J_NR|D4(pKdFE*l z!h!_B6Wk7Vs!t(mfG&KbTng3-T9iuxHatfS)^b)r7k1j1VKD7D?D9(Xg2|=$0TDgJ zCius6>MF&)7ZStbc9H@$lQWrXB!2PP{h!4^?GlMUcCe^QF}aE zN15yKa4{_Nn#2q&hPTS-S>xmb4}bxSiYC~p4q%OOa{z7urAyZKVnc$mU5>nKQTtgd z%m6AdYy1_kVQ+7bgULXE0r(#*d~_FPeB5cdH$Ji#b*uKJ;fxXHD9pfR0buZG2~VIj zJ@wR+mj8a(EDvB{i#*65=GcVn=hK22xEddlGq89GL!K4}sNl!5PuZBkThPMtNTkGS z9q?fSLwOtEgkXkbMLxs~kYjtz6=ZNC=|T5eSBg48J@sRm7?C=z=ZO5%fxVK55S9ZXOF#Sx(aB&26n zd?YI~NpwZM@nBeI1PN`XWMJuw&)0kKH`$KR!yeTF{veJb3O*n^|0nNBS1NIn!^=3< zAby4;5Qb7p-h!kpoQzv_s+|fmKnq|4cpyK))%l2T5&i&F?BpX3V1qMr!cYBA0i@Y}2B+eusMc!7j~|C6 zEw+M)9r$5M0b((d7I^yUhZZwb_7lvo&`vPp&_$3QHU1~fY8HNo&%vMj{@84;48?nQPL3M!n8)nGVe z7LW+W)gYVU_Q#zhF1qV_xw9IGGgL92snFoBWy=Y}M7A6sgBEy$7Wi^!c=h%El{XDo zTg_pnvze)_>DfjUDdQ9vQ$%=!;m~Hem2VC@y&?4yadNFc&c`7_u`vUuT^T4gsum&w z9?UrHI2pJUkf2CBkQE?g;Mq?Gg3tg%C+k4Eg2TvI@R9m1D1wc`-ryH`udOT00A>Ir zuA(jkgrRH7Nkk;Jkgnj0?^_ucaFGBOv!JL*kOE+^20U26 z3>Gkh?k9dN&@~8Nc>9nn;$(mhf@`v2!N4IZ03=74Ax?*2#$iE(!v#xttMI_a3=oBv zf(O=CB2|15_2QQ^9@CjlR~ZqCNb&hXud=>IV*AV_;^zS5Ri>b_v6 z+HI^~-@g9s;qIMmd#ks1JKtE3r3wyz#1l?{8IU{(Tm0c9HqAgGw0%V%XVuMM09=`1 zL}#`uDi@Sv=yoksnuSV3M0O~d_yf#KV+uU9T^d;#syKCi8nQT2J~W&_3G;_FTGJS0 zL~w+126-1m%^{NEc6cHNfBJP%^{^9TH}ecAfBylZb|$5+DJXn9tX_8MyXR^uUIe-esTl$ zLi&LM1kZjBAmso^U_d3J^+Qd6+VYg8Aa6fGaRI@IQbM%$=|BMVu0@a<9b$ zD)=#6l!c8+st2wHpQ>BpQ*H^~1}C|Hkhtqe?vN5LE)f^HZ6#f}bAl@rCZHfN=oLSM zH4ESYU|?{VCLKf&U~VX`pe=u^-| zFawO?Q?<5$fjo!!B8Rbwg_rya_{d6l;Ai-mI|Lsh5T%!#!Uu^BPy}f_@IyBXnPL;s z90;6I+ypZZ+lsCe^l?z|M9*rtb*bIoV}Jo|B;iyM5;mNMcrvG<%9}7?b))IcW1hN( zlU8)UKvB8TM^HSLt!HZ;&O3{uCp$~EOre5$FI{GIt-3*pbUo!B&23es)|gfM-0W&WYSEFS@%tp(y1BJzEVXvawXD z)?PQVH5d<+GAOLz0?=U^is<=LJ82Z@MJ+XYg-SaTONC-76eiL|xZt%ysSRczVnJ-e zcr%*9AZ#MeNAn1m7_9H7^Nn0a`B*NP@?8t| z&65~gC)U8XV6Cza_z;s&@o~Q`&ZZCocZ8e=-zBKQlTcSt-@5CHcb)2!KfUYV`+Pfj zb-n_G;2T9EVawGJKLc8ZN^AfdDo6o2l$m0wEMGWga1RQ@xG^h#K>|ZyfEOEwm|^)1 z3N|FrvZ4}T1Mrx@3?Y3f@L>Hlt2q`Fk@E;%NO6{50WUBkmV(F-7Dj~;(DX#ETx9&f z`o+QSb-Z&5q#ke3S>$|rTxXEXePJKG60rYOpJ7QIOkoy6-XQ@Gn!7P%FT48YZf7< z7trnS`{+{3(BPlxtd}<~aIWtukqLyh*K98S0m2Ipi*_P)!n(gbK{L%YZy!A ztXk^|s-Fu>;u3@-a3|8SWHK73FQlxStFiWf#&vhzWB`#4M~~mUHqDN;;e~mG#hckBDc9pd-okXnlj*LF0^O7GlXl zX|P=wt-8CWh>PT@mwv{XV< zxf=KBVccu`fP^|HX$i20TqMW*PQY{TIUnF2A{2!EPMtc1W1#@!B?~q{4iob}Nh9fYqG=`)Y zX^&Tulxa>IGo%ebT5B2 z0cRb|(DF6mcNtkIAu=yMVZbaWQ33u%<7uLq`8w7~vX!$zmEc03`cx16 zi+CH^Y!<+vV_eD55Sy^5N{ZN@wP0}AD28h<|Hlpo*HwU_))Ht;yd-No;d!vl0l)*m zU}r4&2^2N}2G)N0RKgn;E0;>*6jCTVCoqH74w3ZDm#XbicYIJE9#ndJOjlNqsP6Q_VBE`QJ!h&Y2hq_IePM`>M$i^a=G=$R-9jY5N*CTJm}6FosD z#fz{voU%pey^#8K;v3iUQQ)>*DOKQ}i!%TzWCGrY`2ix^o1 z!4{TRW*6qqp`!tuIB;G3a7WPFaDi}s7#rzCuHg!&Xl#K;MkhOt5nb?nJrJc?H_fnw zpplQ~sz#*)Z!}x$GB`4!=Yx7SLH?t&m20lWQsqLmS8okAgqZPIFFgCP9_kb}U?|ui zpFvJSx$&Rn7X(g_Bf;B%^+~ky@W2K^3OD9(IOrc2ajKW_G-UQ5ivkZdF`ASh*kM_w z*zP29sO`@iLhiy093QORMh-Ttd;4%@ePks-#pmEiTo5v(61g0`1Rn56EYOfs@e^8b za(IB{XF!O6QH?}yxxZHGuBYpRTxaZw!NqdU69OPpF}F+~8!abPT&aIJ8CtfoN`8SN zDS3!gu9{30$!`W~xLV6iOg)lHvq`lDQg*4aT1Mf?>nV(%&M93?BbF zkq-J9NeUuD-A-iy3|yD-xSbpIcZI zkAq+Y(Bd{`7M94b(2h@&9M}VioF;v=N0|o;;|tjEGw`U;WQvL>mSn^l@Z}FhnFh}- zAR?G7G*aa@Wf%HWqGkzUE`Hc#t{OqKqga(u(4hq4UF>1{Nq;0+^oQi{_Y1iV#b5v` zh#l;xgiwMw1;Aj>1B8uh*RF9wd>4MwhbOVIvA*7EwJ7>S4pnv67ZJC^=Rk|wN^I~% zrp27adNfOFzyc0g*~Y4j`+>uTf(LbS8!FUW`_k%W?6(3HM+>*AJfx!?@?ZG@jxfUp z4;w=GA&)QvLM9^w0&ydeEk;r~RQPBXm59KJRGv&-XPhE3plTXBDFj|nB`{*@9i@63 zz)0uolwI%!LQ!&#g?xQD+P&Ev?b9)a6bG4xm_eg@0(CEr$d(%fK{V2Ga&jYDs*q(i z3Qg$wW^Huf_WHpKFatUl(6P7#0aW-ILeZd^c4-;FkiLDSJ{&H%9W>dJnx!IxawrKH zSfVb(Kf<~r$XG@#5EUL58Blm39snU%rJjOw1ob4Or30xVdDUnt4`-d5Bt3!?6pEXu z_Gsy>bYnfnkU^M0)e$16ib{YD+zXPP010jZluWf>!3I|T84aDwadNI=_qcu`FnRAq z$+!q?oICf@i_gEpM37@(1~u5{K@Nfx$O-@k8wxhCLg7tW3&w{PDtomp`+*||=eJKj zxnbjkeGlqL)&dT=7bGnv7JEOqp+G`l(DFjynzKiFAqqC&bU_y9IqmSc;bc2~%vhI7 zgwust!^cFpLJU+!usYy_@|Fk%hl6qjwg{=y7mSN!g^x~iT3Z9mcWsXM$9uQe_HQ!= zfZ_@BQDej)NrT1^=o(-~+|1Lw&Lt2a_Cd%<(L`FVjP{!wmnqFKB$-8!R7wL`LK(TOjZVHjaP< z@BlW(V@OkE8ojiBf(K|(Sdun0zeB;|1xQdV2?jzyiW6B+z++{Zq$tseFA#Ehm|5f} zW8q}N2z`ORP`cbIbvBygi}k^Ny3#9Ed+1eoqY0ktM!v@66k0xz)$)a4Ypr*U>>R#0J?vxnl0MWEM>WXAcq8RY%A2PmS@Gr%&s<%wM8 zR-C>#zC=A$s-PQemYY<0$aauNVLB5k=;TgdY{~F?{G?SH>SOfQIw6+IytgpDHb4vQ zabO1H)yDkke(JxpFU1#W95NFVPKa`y6cG_fI+;JhGK( zDhN@Q{a}3#8!hY&U|{lcHfAVz;C>XMOx~E( z{E*oZXM%La0xF!V4thb+sR1*Vq`Q>H^c5Fn5HFHjfk-%vDlnZWw!LX$|9cgEwfSx@w zb8c#G<~;d}b91xIRS{)D?#JOIHaTl?y(9_i@j%`H0LX#*6QU1>myjw{;<++fgV0B$ zmz9KUq2`Sl(PS=_uhC;Ca}YkQxOxc%woUoD3E`4-%x5RMcAq7_6ypN{RC*LyBqI_C2|wute=) z``(W}8EiP-KC^;{$ukHZSZs2LUh1WAMfM*p3QiQ6L)O-v12OWV3j54cPdo`xo4h7Z zY=Qu`Tuv7}Z^9DPurO}1Z#ux79zLDr={`SCrUPi9B_x1vV$H5h=sKhg$ewz-lrA(( zYcw)CT}%(5M=cnIXPRMTDlA**^b}LP z7pR{s%;Q+hlF1-%v9tsggPt`qq2wN@_tI#>nwl*Qc8GL^k||2Mw4|mRz3OmFOXZnN z5KHINwJuF4kj~NSpgIFPoC*tiWK4Yy8%20uwCoaaL2;!}Ou3lhC%vA<=6YN)13ZA9 z2_7gvgEa{ZVuwjoU|SudlA&M;{~fOtF~3z!?HZG2pHsHAn=g}&CW>^SKI2yyFU5^ zyLE6FX0aqx13UWvf$fC>TbbuZ!D~N1{NE=XMO+n7i(AE~$GT~%0Vdl+3 zLrYi5RWP6x&mX2INBaE%(hRhCgrYRX%jg2o1!5TGR+`KZ%wS&i*>h*lF?yExWM&56 z!V?HGQ$;2`&(6W|5;ZU{4QgHz7~T-#U_rRALy&?H4-@JMKnPW$W{$~7I6;uwVoX#C$Iv6kCCcVXJmi>cfz;0AAI@X4Z9U>g za3&~zDzZ4JKt2Q5P~HaC%mQX$-?pFepfCd|Ew1&jg(o71U`e$}0k+*GBELia5r(nP*Uw8BYL%kcDK% z>WUDq$T6VBg$B;b%8JMBjzmZpM8mqs-^4UCo2wYv3b_SuB!y0B)XX@8VW?j|f6!;? zaL}C2BcCpnXd;7XFtgSuhtgkoFgQ7W2DuHwBO)4Z zOeBt(@$B()+90D*@-R26U=re8TCQm1%eiuc=>(|kfJp>LxFwLgsLJxAAv&Jm>f}8W zwAducUXU#jdIwSf!5G8hv&AyQOhAaoGd2Mac+B&#rw5f^f=%eclo7)~K!+(OEMz$F zKUkEX@zz^!-4(oi3W(*>WLM#Lsr1RGIK$`t6-kL&ybb5_($vWlQ>RZ6s=RXSB~lx7 z!-5(lMHTrAguuf9H+Vz?9X5WjU)iPoIOnasja}XaD(v9^Wv{I5<&!aE^3zHDP+zyf zL!FtO>Xy`+8?YBXQU}{7vCbS!WMDe!g(ma`y0f5Lx}(~XCooG5gB)lOV+xSLK&BJP zKIAQ=(PLrJL#`keBQ)@n(|`?1yJ=MO1fn#!(%BJ+rWT#(>`?qf1PpqW0ZEfiR2onT zKyx2rd8*y;#ndYn4hp5{?0D%zUGUO2$^?P2C~*84GMnCrkxET1^pVig=y#IhdpY!8DT)K3L9L78EyaTC;R4_gV zfB`(SQE7+g_yS%u-{Pd|a z{JkDO^~%d9$!)y!;>&1n$m}4YZ}3txLD05>WeILjX8}h*5us9vzj25b;`&c4>g}W* zE3;M>uhx^O@LRWSi@$e6oz1=$Z`F;khi#7ksFUlthFR)XIw)ZF{IIVI)qK6f1U2R{ z6Bjs9;WLc5Zp5`D(JA+eX3vteyORB+ zyT3W!3!9awQPR?7;EJL4biB~~j(m?PisRL8uMd+4F9U7{tgg@L@9tqfE$#xL0l7%1 z62Jfpuz?Y)x+^$g0mmU=Se}NAU!P2MeCnT0;RM2g&_X};Nj}#vD$b5ofL-r&+N1%G zAA4!)L97gBH4@kihO@W0PIfgmQdX+{9$n`0)aQpGT` zB{-XyBc%$=lUaocE#(h{sCz=roMS4I3_4`cVwi4NNsky@&baUR@LQQIUo zaeH~plYyk6lo{EiH>4|M)=Ef*uue*pJhRhKx0zuUh^1gkkacuZTJeWD10A!Z5Fnh= z*%3}KZG?=c0d*8DFL;`2s3L1bc4&de#{e7fFgPuR*rnA~shcbvPBetT67|u8GI6d> z*_QJ^>EPrj!VlI8KPC-YZQG{*k-%`DJMELOp-y|BC!e220|W8|=EV>pa)_Zg zlxv;rr$u6rCJqXlK!x}hq!g^etkh*8XyOVu;eSVivACWlt7tMWl_H2MfI4`Hlp<6t z3?Eh%glsf6fFd%I40t9YfEh|%hEk{#i7khN*4M?A6$&FHGsu2Y5TPmy?+Zu(mw1s` zArcWHh{H*<$vLjRx++yx1c4$^ra?T35xDX;20u_sQj>jRajw7XW(1IxD<1k~=*6$M@KJu{1^GssKM;<)6^XwVaEYgJ< z)=XBB@&wjO`Cy*;%xL0x0~kGx@Ng#)@kJ4fi3O|{7nfmzLN^bF!_j!0p&@3zj6^Wm z3nm0G8kr^z2C5<1$r>5*M6PMphEcPO$`%l!rPA;%g+1zWFVh4^X2R)2Dg!?Q4Nzht zZ>LDxK$5xPhKGiDEv36)W5G~)-D@y3`W;*SU!SlZ$7Dgcb6nNFE$hBi|8Wq@Na zLOqAts|E_90aWX4*2jD0;hs@j%eJ@k&9y|X!cZm}U#axqfQRl|U|In$uI~1U8Nx!p1AO8qYtc;uO|+8`g_d>GLX8 z3jLhSs3M~uqKFl_s364(7Yeg;jZ*M}Gm2?|P;L$RR!S%Tm)CuO*CJo6 zV!B$j;&C}oQGk8!>&IVwK{6aKzCcx$mvT7BaG+4k%Sv^?==d6;3!cb}FEN)146!DQ zScwgg!-kCqiy0t{=S+0k;4|7bI`b^*b*3OtEw+fiSPc=o8(_Cvk{v+r|`TbGh%Km=4 z)k-Im&_?kmG-kl$Anif6gP4UqiiZB-hk6D8+zv!K;G+|=FiKS?GxRN(!N$>i1KZ=v z{ROY}Sv~Fg$MlE6fLe~NGme$tX>@&6dncE4tE-wk8L?I@<82@r&Tl^g7;CC8Wj#k) z6CQ>V(=slOEADl#9H*PIFkHS~44M_rBME0Zhy4*U=&IBiF@(PMSyQX zCIe0vY8EGq8N4){F0!kyIq&G1PxKb=M!>N@*x8|90_30uOG*P*<0Dp@+pb+@&qpZw zKnA;V<%6qNRrlNV>x3sHGg!b0y`lY#qnPnh(ZsCKLLX;uZ+LH?AOd}FDyxJc{E!n4 zFc7s?Z&hrM2!dt|@`+WKHufIET;NKvV$#nZX?Eu%u`H`vt>##WO4R!^%dohr)mCp1b_Oc3TR?LhlxhQe zt@cKDV{^E>gGX@?t!iwuWG^ONO**Lnj|9D^vtkm zHPd;M@dKvYbXvRZdtWq0|KN1*R|0|ppQbn5kt`Z}hJ~h2G%4*CS~^Qhi)^#dTFU{Q zMw7t;seAzvC`r#!g~_hPvO)~-q-c}A+#8Lu>9meh;E)oV{5B$xvWI#K5Q5u5*9rv{FoU`)m@#%Ta62@3jGc_P z0vp%n2sDt?p(w%6KTVy01mgh^!=bGe{2=40^u5=iXTjNEpb0b5TJX#1Od*-UKNKtE zPwS9L?Z$T5Y^JjnxEEEo?e`ygTaQb%9z-uUY(SxirIIiy@LfPD(yB;}GBV}v-4&=F z^r9iYMGFg>986WEcneue!x%0EUIh*W{sm}Ju2$(MgEyMbqJCa5%Iq^Ku_n`W*JT|q z0P4cl2K4d4kdvM8LrtvE2tlgz-jh|K#~46P=A3zVUIkY}_kF+vDZC;k9PDWh*rqi)ExWc8#nMOa5aDpR$Wgaase^G3|6wDfCk7h#tb%eR^#nr z#+^A-f|nMTDPMpL)vDrBQXGLZLGCf?_9WHF@4Jy@<|!3Y%dhnYWv`Vl**T+Z4=74Q7JhN-b`!_`uP%c{X`78pB0=8 z{Frz;g)#^ID+Y2MR73Lgg1D|Qur2C`N=`YHsPmtqWwyPdJZ(m@WOO|{Fnb0vC^acLu)t|!4ffKZ*!OS>I zfyqRR@FS*0e3FSoR64naZ-KV~Z0Jx0pM#ZtH=GRZTX4%lcrww?077P)u{i!{H|A9T z#+p*xp~%U+Bl<^d&*#D=7%^+iLJO)v!*aY_nl9G3+uX2PeS!IIw|dL5hX;{4`B-(SnD8o(JkbRnzSD zdVEomtC)DUzW7eA;4i=Yl3)FqG`Tut#Sqe=Y+8T?UZqND z`3=*n(^PH{j08oaxR+ZKKw`$58mz)`8RB$=S_Xdq{T) zfZ>Eax>u@>UvF>JTfLysB3rq%_=ngUH4vmr&>nEX!HTA}RbO0MB`Ywb~U@Q6k?7#PWXm9b<&XVBc(37UPhG-z(CF!#`M9NWbc zfVG8(K}dp1F%2UK3DRZH6(0jIl1loAsnAZ)a3NT@9w4zjq|bDh*p_v76s0l=1`qJ; z9uoedwMOZLg_dodofh}v=_&czTU^HwH5jQ%UkL{1?rR@%@&Pz9eJeD;>Ga8Ya56B& z_*{j?K^4!o%ujCKgaf8p+@fME5CUwF;ZW0xqD4c+QOp?c(P%Ng{q==D(>=rnO8&cC zK>&k#34TUI$jBmsMbH4XJDtfBo0J)ZR^M=%beco3Fw1tm%z%Ng&qVcFdypwwjAX~7 zsD-e${E}hC7+pYRBVSB2&OM*cWzv7Rb9-fZ38yB*Xr*+T@+`_6I2J$!?QkF$k%s9p zrfAsP9BdB;gng>V^vH7blw-GgLbT|o9DP)_XJP?>nF0zetcZ?tDZh!U8NfjPiy84~ zay%m+IFpa?P8@_27*s~xoSfdR-~ayOzxfE{V5P~M3#0P1@zzZR8&Oq-<`e(}z5=C6y+RU*Fj63?DZ7 zyG$*l-~yE!q9!iII-`n~siZFBYETzJMHCG`oD4KIXhxwd((G*YwuZwIO&==QP!&>H zPMHO`no$TPtAV#cqdJ}k4#(@fN6>;J5R&M2t}%GL&N0u}|LT}~d$aG_78?9yHI%D? zYLDtfxup72XrD(cP&LzEqA4~)5EZ1b;9)3gXP7jlA>EfLm?eb&JHzJozSkMn+FM?ypR3qqizd?04m-V_eLHB86rs1ws8(Qj zP}G2kL1q=WV4+aLL=r+3s-g^dHXWzGwM`hJa3V@v@U`P&oUt$eYB$jh<+!}Un>pqGLbSqyq6q`b$bwQsQBCv;Iha=ucw7cc z=&}bq*k%DR#wm^QB|h_wg9VjTSkI*kj18zV5)q*sn%b#vDrSc3WlIJ`Q34aoYuCaa zH4&rMhTmOfZb-Y!pv8J`8)#|w2bNbS=fQjs^s=ajtf9|ycZJ$WDnlhTlVA)&N(&Dg zp6B&9x1jo|YLlXB-e-;QLIC6Rp2Q#o=P!N+)mJX}@4x?^m<0e}|8p;T?Md)tHmKY} z5T{B)|0?AY`IUI?R~`Y!Lzqa=%K-^gHx$nthvSNZ5ZGUkMrm;)Q~@xKWhx!aT?(h@WuVk4(^2=ZXiz{O-}E$`a7M$$m?#> zcy3q@!;Y#7FpM}JMl_q5$p%104C=@FdZAFL)oT5IpA!d^h{{Wc2*IE8IZ5QXC(CJj z0#`f;@x>Qkklw)I`1$9bNktH1aNYB@!r{>Pp&>*+dAy$rIike)C_oPNTM97*COmrD zKI7?pKUf&#p#G@hkt3Q$9M}O>&{@B^xZ*SeNH9OERI=$US zd()_SMLIsJCJb+w+#HK;Gsdn)B7k>MwxEZs)#`9cMIoAw(^Ld^83s9U7r=m@e)SOe)xfEDw@MMC=g3O z{q)lp@lKw;5p>|Q>d&pe`=fxN_sk{{f4-^h(-?WcEA`Vtx2fz z$%IP*RZ~r6wwYnjHiL#8)X~qNdxUl|M6{SCK=&jRI8n3ymt@H!~3lH`adbId~ZTicf3s-o&0)&Y!~SEu)!l>`R92%Ta2 zmjn0%FlJ~TIh0sHEHKRiTyB5|(-Bb7U5KhDz(!OUI@2}fUbxg%Dp&5 z2qjdkDlbtj=Fn47s1sFm)xW5Tk;(-Dd+gt>V{-S?OF(D(+e50YVsWFBXBHgo(Ev?-lu&s*)J9l3%ofCfE>7Sxono) z1~u3*Y#hajaqyzy0rx_$PryTg#5lUq8$445I}y6zlmHld1w(l9U#_Myw0qO%G zc!XP?tZzBazg}Kd&8upq4Ib7N{phiHGO?ba-IZx-d8Ef(zi2v8K^X(USX5}C>xl$p z3qFki#_?aNJSrAA84Ku8#q<3_E>Ee5`+n|L2#>t!x@+xA1=UM=7gX#)1KW7}4O zLFspH|7Ky~8VSuPU;#yd1XeaazVW-8H*Vayb(7~XO5x7!Ta05?A~*3E5k(4Jtaa98 zDw(uQGYo>cxuuo(tvgG%nS8i(m%;1YE-tMu(}Gf_r=&&RlbP?X=R1xIb=9`4;b0(u z@m9_Sc~P;z%UWRUd_e7Q1VK>q7$+P0UVuC%Q%Ttl0`%^X`_Z@C+iDZVEBuv5k5C@` z#e!ryKr}a(Naq=J$Qbw4L@J)nGCp->WfeEW@&YoRw&fv34$~7}Cu4}v+9T!ivTt0T zFBT9BoQwsgl`wHWesTF9xf6LLihijV<(?c%nwXm_7&fAOOlPGr)pkRgPU7(doUX9Z zww=HaTDIe$%|~DYWefc*Vey^JmoKj(7I*~M zZ+Ciqw-zuJpxtf@V4RaPLO!Ng;CKuCDP>qeDpNv3i%BC0f2IpXH)w{azNiuA%Gi9y3EB^N=9b8#BThZGCEumuhlR@bvjtBIvm=C)=bgc?jG zF-_n1k%NvL3`t|WurcL{Vu5pD0d&W66$H2#tyrwsDoQa7W-8OLDo(~Z`1<7|iUnTU z0tX9ar(SFH+^|h1Bb(3D;f{Rt@kFqfeiiu#v4B|MtXSaRt}(xui6NGmKX)nYOJ&xAymS9%GiTBF7R7hy~7y1^)X&#SQ#s z*YSew!B9<1l|?Kd77zz~A0St*prdvuL5etX~lm-5Go(Tn;+k^dw zj82UfsjX@|$gNmFEHKFef4#I74J8o|W0G}bpIAUF@T>*q<`i*jtkrk_!5*tDVga#$ zSYV0;(sNTDklkVdv4B`$f(8Ehw-a{CF0p`EKrArD0tX9I9+2H)0kMEsV1fk%GbR{C zc8LYV0#hv@m@(Bf@_<-CEHJ?Wf*BKxBD=%_Vu7g^5X_ir8hJo0AQqTl0l|z3Mv+}& z0kObT3kYURHH|zV77z|*4fDb literal 0 HcmV?d00001 diff --git a/plugins/model/model.cpp b/plugins/model/model.cpp new file mode 100644 index 0000000..79db9e8 --- /dev/null +++ b/plugins/model/model.cpp @@ -0,0 +1,1024 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.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 0 + 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, + file.getName()); + 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 0000000..b0145f5 --- /dev/null +++ b/plugins/model/model.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_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 0000000..b92e0db --- /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/plugin.cpp b/plugins/model/plugin.cpp new file mode 100644 index 0000000..51aa05b --- /dev/null +++ b/plugins/model/plugin.cpp @@ -0,0 +1,188 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "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(const char *name, byte **buffer, int *bufSize) +{ + *bufSize = vfsLoadFile(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/shaders/CMakeLists.txt b/plugins/shaders/CMakeLists.txt new file mode 100644 index 0000000..04de80f --- /dev/null +++ b/plugins/shaders/CMakeLists.txt @@ -0,0 +1,8 @@ +radiant_plugin(shaders + plugin.cpp + shaders.cpp shaders.h +) + +find_package(GLIB REQUIRED) +target_include_directories(shaders PRIVATE ${GLIB_INCLUDE_DIRS}) +target_link_libraries(shaders PRIVATE ${GLIB_LIBRARIES}) diff --git a/plugins/shaders/plugin.cpp b/plugins/shaders/plugin.cpp new file mode 100644 index 0000000..c302d9b --- /dev/null +++ b/plugins/shaders/plugin.cpp @@ -0,0 +1,86 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" +#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("tga") + { + } + + ImageModuleRef &getBitmapModule() + { + return m_bitmapModule; + } +}; + +class MaterialAPI { + ShaderSystem *m_shadersq3; +public: + typedef ShaderSystem Type; + + STRING_CONSTANT(Name, "mat"); + + MaterialAPI(ShadersDependencies &dependencies) + { + g_shadersExtension = "mat"; + g_shadersDirectory = "textures/"; + g_enableDefaultShaders = false; + g_useShaderList = false; + g_bitmapModule = dependencies.getBitmapModule().getTable(); + Shaders_Construct(); + m_shadersq3 = &GetShaderSystem(); + } + + ~MaterialAPI() + { + Shaders_Destroy(); + } + + ShaderSystem *getTable() + { + return m_shadersq3; + } +}; + +typedef SingletonModule > MaterialModule; +MaterialModule g_MaterialModule; + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer &server) +{ + initialiseModule(server); + g_MaterialModule.selfRegister(); +} diff --git a/plugins/shaders/shaders.cpp b/plugins/shaders/shaders.cpp new file mode 100644 index 0000000..25d36e0 --- /dev/null +++ b/plugins/shaders/shaders.cpp @@ -0,0 +1,1566 @@ +/* + 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 "globaldefs.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; +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 << ".tga"; + 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; + int m_iPolygonOffset; + + // 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 parseMaterial(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; +}; + +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 ¶ms) +{ + 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 parseMaterial(tokeniser); +} + +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 ¶ms, 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 ¶ms, const ShaderArguments &args) +{ + return BlendFunc(BLEND_ONE, BLEND_ZERO); +} + +qtexture_t * +evaluateTexture(const TextureExpression &texture, const ShaderParameters ¶ms, 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 ¶ms, const ShaderArguments &args) +{ + const char *result = evaluateShaderValue(value.c_str(), params, args); + float f = 0.0f; + if (!string_parse_float(result, f)) { + globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n"; + } + return f; +} + +BlendFactor evaluateBlendFactor(const ShaderValue &value, const ShaderParameters ¶ms, 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: + 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; + } + + int getPolygonOffset() const + { + return m_template.m_iPolygonOffset; + } + + // 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.tga" : "shadernotex.tga"); + m_pTexture = GlobalTexturesCache().capture(LoadImageCallback(0, loadBitmap), name.c_str()); + } + } + } + + void unrealise() + { + GlobalTexturesCache().release(m_pTexture); + + if (m_notfound != 0) { + GlobalTexturesCache().release(m_notfound); + } + } + + // 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 ¶ms, + 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; + } +}; + +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::parseMaterial(Tokeniser &tokeniser) +{ + 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, "polygonoffset")) { + if (Tokeniser_getInteger(tokeniser, m_iPolygonOffset) == false) { + m_iPolygonOffset = 1; + } + m_nFlags |= QER_POLYOFS; + } 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") || string_equal_nocase(token, "diffusemap")) { + 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; + +#if defined(WIN64) || defined(WIN32) +char * +strndup (const char *s, size_t n) +{ + char *result; + size_t len = strlen (s); + + if (n < len) + len = n; + + result = (char *) malloc (len + 1); + + if (!result) + return 0; + + result[len] = '\0'; + return (char *) memcpy (result, s, len); +} +#endif + +char* m_substring(const char* str, size_t begin, size_t len) +{ + if (str == 0 || strlen(str) == 0 || strlen(str) < begin || strlen(str) < (begin+len)) { + return 0; + } + + return strndup(str + begin, len); +} + +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) + ShaderTemplatePointer shaderTemplate(new ShaderTemplate()); + shaderTemplate->setName( m_substring( filename, 0, strlen( filename) - 4 ) ); // EUKARA + + g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)); + + bool result = shaderTemplate->parseMaterial(tokeniser); + if (result) { + // do we already have this shader? + if (!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(), + ShaderDefinition( + shaderTemplate.get(), + ShaderArguments(), + filename))).second) { + #if GDEF_DEBUG + globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() + << " is already in memory, definition in " << filename << " ignored.\n"; + #endif + } + } else { + globalErrorStream() << "Error parsing material " << 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 material " << filename << "\n"; + Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream()); + ParseShaderFile(tokeniser, filename); + tokeniser.release(); + file->release(); + } else { + globalOutputStream() << "Unable to read material " << filename << "\n"; + } +} + +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"; + } +} + +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 any shaderlist.txt:\n"; + } + globalOutputStream() << "\t" << filename << "\n"; + } +} + +typedef ReferenceCaller 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)); + } +} + +/* + ================== + 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); + } +} + +void ShaderList_addFromArchive(const char *archivename) +{ + const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath"); + if (string_empty(shaderpath)) { + return; + } + + StringOutputStream shaderlist(256); + shaderlist << DirectoryCleaned(shaderpath) << "shaderlist.txt"; + + Archive *archive = GlobalFileSystem().getArchive(archivename, false); + if (archive) { + ArchiveTextFile *file = archive->openTextFile(shaderlist.c_str()); + if (file) { + globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n"; + BuildShaderList(file->getInputStream()); + file->release(); + } + } +} + +#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 (QUAKE4) { + GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 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); + } + + GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true); + DumpUnreferencedShaders(); + } else { + GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 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; + +// wait until filesystem and is realised before loading anything +std::size_t g_shaders_unrealised = 1; +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 MaterialSystem : 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 ¬ify) + { + g_ActiveShadersChangedNotify = notify; + } + + void attach(ModuleObserver &observer) + { + g_observers.attach(observer); + } + + void detach(ModuleObserver &observer) + { + g_observers.detach(observer); + } + + void setLightingEnabled(bool enabled) + { + + } + + const char *getTexturePrefix() const + { + return g_texturePrefix; + } +}; + +MaterialSystem g_MaterialSystem; + +ShaderSystem &GetShaderSystem() +{ + return g_MaterialSystem; +} + +void Shaders_Construct() +{ + GlobalFileSystem().attach(g_MaterialSystem); +} + +void Shaders_Destroy() +{ + GlobalFileSystem().detach(g_MaterialSystem); + + if (Shaders_realised()) { + Shaders_Free(); + } +} diff --git a/plugins/shaders/shaders.h b/plugins/shaders/shaders.h new file mode 100644 index 0000000..4c9859a --- /dev/null +++ b/plugins/shaders/shaders.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_SHADERS_H ) +#define INCLUDED_SHADERS_H +void Shaders_Construct(); +void Shaders_Destroy(); +class ShaderSystem; +ShaderSystem &GetShaderSystem(); +extern const char *g_shadersExtension; +extern const char *g_shadersDirectory; +extern bool g_enableDefaultShaders; +extern bool g_useShaderList; +struct _QERPlugImageTable; +extern _QERPlugImageTable *g_bitmapModule; +#endif diff --git a/plugins/shaders/shadersq3.def b/plugins/shaders/shadersq3.def new file mode 100644 index 0000000..ab66413 --- /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/vfspk3/CMakeLists.txt b/plugins/vfspk3/CMakeLists.txt new file mode 100644 index 0000000..a55e9de --- /dev/null +++ b/plugins/vfspk3/CMakeLists.txt @@ -0,0 +1,12 @@ +radiant_plugin(vfspk3 + archive.cpp archive.h + vfs.cpp vfs.h + vfspk3.cpp + ) + +find_package(GLIB REQUIRED) +target_include_directories(vfspk3 PRIVATE ${GLIB_INCLUDE_DIRS}) +target_link_libraries(vfspk3 PRIVATE ${GLIB_LIBRARIES}) + +target_include_directories(vfspk3 PRIVATE filematch) +target_link_libraries(vfspk3 PRIVATE filematch) diff --git a/plugins/vfspk3/archive.cpp b/plugins/vfspk3/archive.cpp new file mode 100644 index 0000000..f4fc25b --- /dev/null +++ b/plugins/vfspk3/archive.cpp @@ -0,0 +1,169 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" + + +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 0000000..2704d25 --- /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 0000000..0829073 --- /dev/null +++ b/plugins/vfspk3/vfs.cpp @@ -0,0 +1,990 @@ +/* + 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 "globaldefs.h" + +#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" +#include "filematch.h" +#include "dpkdeps.h" + + +const int VFS_MAXDIRS = 64; + +#if GDEF_OS_WINDOWS +#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 +#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 char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1]; +static int g_numForbiddenDirs = 0; +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 Archive *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"; + + return entry.archive; + } + + return 0; +} + +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; + +Archive *AddPk3Dir(const char *fullpath) +{ + if (g_numDirs == VFS_MAXDIRS) { return 0; } + + strncpy(g_strDirs[g_numDirs], fullpath, PATH_MAX); + g_strDirs[g_numDirs][PATH_MAX] = '\0'; + g_numDirs++; + + { + archive_entry_t entry; + entry.name = fullpath; + entry.archive = OpenArchive(fullpath); + entry.is_pakfile = false; + g_archives.push_back(entry); + + return entry.archive; + } +} + +// for Daemon DPK vfs + +Archive *AddDpkDir(const char *fullpath) +{ + return AddPk3Dir(fullpath); +} + +struct pakfile_path_t { + CopiedString fullpath; // full pak dir or pk3dir name + bool is_pakfile; // defines is it .pk3dir or .pk3 file +}; + +typedef std::pair PakfilePathsKV; +typedef std::map PakfilePaths; // key must have no extension, only name + +static PakfilePaths g_pakfile_paths; + +void AddDpkPak(const char *name, const char *fullpath, bool is_pakfile) +{ + pakfile_path_t pakfile_path; + pakfile_path.fullpath = fullpath; + pakfile_path.is_pakfile = is_pakfile; + g_pakfile_paths.insert(PakfilePathsKV(name, pakfile_path)); +} + +// takes name without ext, returns without ext +static const char *GetLatestDpkPakVersion(const char *name) +{ + const char *maxversion = 0; + const char *result = 0; + const char *pakname; + const char *pakversion; + int namelen = string_length(name); + + for (PakfilePaths::iterator i = g_pakfile_paths.begin(); i != g_pakfile_paths.end(); ++i) { + pakname = i->first.c_str(); + if (strncmp(pakname, name, namelen) != 0 || pakname[namelen] != '_') { continue; } + pakversion = pakname + (namelen + 1); + if (maxversion == 0 || DpkPakVersionCmp(pakversion, maxversion) > 0) { + maxversion = pakversion; + result = pakname; + } + } + return result; +} + +// release string after using +static char *GetCurrentMapDpkPakName() +{ + char *mapdir; + char *mapname; + int mapnamelen; + char *result = 0; + + mapname = string_clone(GlobalRadiant().getMapName()); + mapnamelen = string_length(mapname); + + mapdir = strrchr(mapname, '/'); + if (mapdir) { + mapdir -= 12; + if (strncmp(mapdir, ".dpkdir/maps/", 13) == 0) { + *mapdir = '\0'; + mapdir = strrchr(mapname, '/'); + if (mapdir) { mapdir++; } + else { mapdir = mapname; } + result = string_clone(mapdir); + } + } + + string_release(mapname, mapnamelen); + return result; + +} + +// prevent loading duplicates or circular references +static Archives g_loaded_dpk_paks; + +// actual pak adding on initialise, deferred from InitDirectory +// Daemon DPK filesystem doesn't need load all paks it finds +static void LoadDpkPakWithDeps(const char *pakname) +{ + Archive *arc; + ArchiveTextFile *depsFile; + + if (pakname == NULL) { + // load DEPS from game pack + StringOutputStream baseDirectory(256); + const char *basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame"); + baseDirectory << GlobalRadiant().getGameToolsPath() << basegame << '/'; + arc = AddDpkDir(baseDirectory.c_str()); + depsFile = arc->openTextFile("DEPS"); + } else { + const char *und = strrchr(pakname, '_'); + if (!und) { + pakname = GetLatestDpkPakVersion(pakname); + } + if (!pakname || g_loaded_dpk_paks.find(pakname) != g_loaded_dpk_paks.end()) { + return; + } + + PakfilePaths::iterator i = g_pakfile_paths.find(pakname); + if (i == g_pakfile_paths.end()) { + return; + } + + if (i->second.is_pakfile) { + arc = InitPakFile(FileSystemQ3API_getArchiveModules(), i->second.fullpath.c_str()); + } else { + arc = AddDpkDir(i->second.fullpath.c_str()); + } + g_loaded_dpk_paks.insert(pakname); + + depsFile = arc->openTextFile("DEPS"); + } + + if (!depsFile) { + return; + } + + { + TextLinesInputStream istream = depsFile->getInputStream(); + + CopiedString line; + char *p_name; + char *p_version; + while (line = istream.readLine(), string_length(line.c_str())) { + if (!DpkReadDepsLine(line.c_str(), &p_name, &p_version)) { continue; } + if (!p_version) { + const char *p_latest = GetLatestDpkPakVersion(p_name); + if (p_latest) { LoadDpkPakWithDeps(p_latest); } + } else { + int len = string_length(p_name) + string_length(p_version) + 1; + char *p_pakname = string_new(len); + sprintf(p_pakname, "%s_%s", p_name, p_version); + LoadDpkPakWithDeps(p_pakname); + string_release(p_pakname, len); + } + string_release(p_name, string_length(p_name)); + if (p_version) { string_release(p_version, string_length(p_version)); } + } + } + + depsFile->release(); +} + +// end for Daemon DPK vfs + +// ============================================================================= +// Global functions + +// reads all pak files from a dir +void InitDirectory(const char *directory, ArchiveModules &archiveModules) +{ + int j; + + g_numForbiddenDirs = 0; + StringTokeniser st(GlobalRadiant().getGameDescriptionKeyValue("forbidden_paths"), " "); + for (j = 0; j < VFS_MAXDIRS; ++j) { + const char *t = st.getToken(); + if (string_empty(t)) { + break; + } + strncpy(g_strForbiddenDirs[g_numForbiddenDirs], t, PATH_MAX); + g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = '\0'; + ++g_numForbiddenDirs; + } + + for (j = 0; j < g_numForbiddenDirs; ++j) { + char *dbuf = g_strdup(directory); + if (*dbuf && dbuf[strlen(dbuf) - 1] == '/') { + dbuf[strlen(dbuf) - 1] = 0; + } + const char *p = strrchr(dbuf, '/'); + p = (p ? (p + 1) : dbuf); + if (matchpattern(p, g_strForbiddenDirs[j], TRUE)) { + g_free(dbuf); + break; + } + g_free(dbuf); + } + if (j < g_numForbiddenDirs) { + printf("Directory %s matched by forbidden dirs, removed\n", directory); + return; + } + + if (g_numDirs == VFS_MAXDIRS) { + 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"; + + Archives archives; + Archives archivesOverride; + const char *ignore_prefix = ""; + const char *override_prefix = ""; + bool is_pk3_vfs, is_pk4_vfs, is_dpk_vfs; + + is_pk3_vfs = GetArchiveTable(archiveModules, "pk3"); + is_pk4_vfs = GetArchiveTable(archiveModules, "pk4"); + is_dpk_vfs = GetArchiveTable(archiveModules, "dpk"); + + if (!is_dpk_vfs) { + // 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_"; + } + } + + for (;;) { + const char *name = g_dir_read_name(dir); + if (name == 0) { + break; + } + + for (j = 0; j < g_numForbiddenDirs; ++j) { + const char *p = strrchr(name, '/'); + p = (p ? (p + 1) : name); + if (matchpattern(p, g_strForbiddenDirs[j], TRUE)) { + break; + } + } + if (j < g_numForbiddenDirs) { + continue; + } + + const char *ext = strrchr(name, '.'); + char tmppath[PATH_MAX]; + + if (is_dpk_vfs) { + if (!!ext && !string_compare_nocase_upper(ext, ".dpkdir")) { + snprintf(tmppath, PATH_MAX, "%s%s/", path, name); + tmppath[PATH_MAX] = '\0'; + FixDOSName(tmppath); + AddSlash(tmppath); + AddDpkPak(CopiedString(StringRange(name, ext)).c_str(), tmppath, false); + } + } + + if (is_pk3_vfs || is_pk4_vfs) { + if (!!ext && (!string_compare_nocase_upper(ext, ".pk3dir") + || !string_compare_nocase_upper(ext, ".pk4dir"))) { + snprintf(tmppath, PATH_MAX, "%s%s/", path, name); + tmppath[PATH_MAX] = '\0'; + FixDOSName(tmppath); + AddSlash(tmppath); + AddPk3Dir(tmppath); + } + } + + // GetArchiveTable() needs "pk3" if ext is ".pk3" + if ((ext == 0) || *(ext + 1) == '\0' || GetArchiveTable(archiveModules, ext + 1) == 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) { + if (!string_compare_nocase_upper(ext, ".dpk")) { + if (is_dpk_vfs) { + archives.insert(name); + } + } else { + archivesOverride.insert(name); + } + continue; + } + + archives.insert(name); + } + + g_dir_close(dir); + + // add the entries to the vfs + char *fullpath; + if (is_dpk_vfs) { + for (Archives::iterator i = archives.begin(); i != archives.end(); ++i) { + const char *name = i->c_str(); + const char *ext = strrchr(name, '.'); + if (!string_compare_nocase_upper(ext, ".dpk")) { + CopiedString name_final = CopiedString(StringRange(name, ext)); + fullpath = string_new_concat(path, name); + AddDpkPak(name_final.c_str(), fullpath, true); + string_release(fullpath, string_length(fullpath)); + } + } + } + if (is_pk3_vfs || is_pk4_vfs) { + for (Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i) { + const char *name = i->c_str(); + const char *ext = strrchr(name, '.'); + if (!string_compare_nocase_upper(ext, ".pk3") + || !string_compare_nocase_upper(ext, ".pk4")) { + fullpath = string_new_concat(path, i->c_str()); + InitPakFile(archiveModules, fullpath); + string_release(fullpath, string_length(fullpath)); + } + } + for (Archives::iterator i = archives.begin(); i != archives.end(); ++i) { + const char *name = i->c_str(); + const char *ext = strrchr(name, '.'); + if (!string_compare_nocase_upper(ext, ".pk3") + || !string_compare_nocase_upper(ext, ".pk4")) { + fullpath = string_new_concat(path, i->c_str()); + InitPakFile(archiveModules, fullpath); + string_release(fullpath, string_length(fullpath)); + } + } + } + } 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; + g_numForbiddenDirs = 0; + + g_pakfile_paths.clear(); + g_loaded_dpk_paks.clear(); +} + +const int VFS_SEARCH_PAK = 0x1; +const int 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) +{ + const char *best = ""; + for (archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) { + if (string_length((*i).name.c_str()) > string_length(best)) { + if (path_equal_n(absolute, (*i).name.c_str(), string_length((*i).name.c_str()))) { + best = (*i).name.c_str(); + } + } + } + + return best; +} + + +class Quake3FileSystem : public VirtualFileSystem { +public: + void initDirectory(const char *path) + { + InitDirectory(path, FileSystemQ3API_getArchiveModules()); + } + + void initialise() + { + load(); + globalOutputStream() << "filesystem initialised\n"; + g_observers.realise(); + } + + void load() + { + ArchiveModules &archiveModules = FileSystemQ3API_getArchiveModules(); + bool is_dpk_vfs = GetArchiveTable(archiveModules, "dpk"); + + if (is_dpk_vfs) { + const char *pakname; + g_loaded_dpk_paks.clear(); + + // Load DEPS from game pack + LoadDpkPakWithDeps(NULL); + + // prevent VFS double start, for MapName="" and MapName="unnamed.map" + if (string_length(GlobalRadiant().getMapName())) { + // load map's paks from DEPS + char *mappakname = GetCurrentMapDpkPakName(); + if (mappakname != NULL) { + LoadDpkPakWithDeps(mappakname); + string_release(mappakname, string_length(mappakname)); + } + } + + g_pakfile_paths.clear(); + g_loaded_dpk_paks.clear(); + } + } + + void clear() + { + // like shutdown() but does not unrealise (keep map etc.) + Shutdown(); + } + + void refresh() + { + // like initialise() but does not realise (keep map etc.) + load(); + globalOutputStream() << "filesystem refreshed\n"; + } + + 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, bool pakonly) + { + for (archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) { + if (pakonly && !(*i).is_pakfile) { + continue; + } + + if (path_equal((*i).name.c_str(), archiveName)) { + return (*i).archive; + } + } + return 0; + } + + void forEachArchive(const ArchiveNameCallback &callback, bool pakonly, bool reverse) + { + if (reverse) { + g_archives.reverse(); + } + + for (archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) { + if (pakonly && !(*i).is_pakfile) { + continue; + } + + callback((*i).name.c_str()); + } + + if (reverse) { + g_archives.reverse(); + } + } +}; + + +Quake3FileSystem g_Quake3FileSystem; + +VirtualFileSystem &GetFileSystem() +{ + return g_Quake3FileSystem; +} + +void FileSystem_Init() +{ +} + +void FileSystem_Shutdown() +{ +} diff --git a/plugins/vfspk3/vfs.h b/plugins/vfspk3/vfs.h new file mode 100644 index 0000000..277c7b3 --- /dev/null +++ b/plugins/vfspk3/vfs.h @@ -0,0 +1,42 @@ +/* + 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 0000000..4f16f73 --- /dev/null +++ b/plugins/vfspk3/vfspk3.cpp @@ -0,0 +1,84 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" +#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/vfsq3.def b/plugins/vfspk3/vfsq3.def new file mode 100644 index 0000000..885ffeb --- /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/radiant/CMakeLists.txt b/radiant/CMakeLists.txt new file mode 100644 index 0000000..170dda1 --- /dev/null +++ b/radiant/CMakeLists.txt @@ -0,0 +1,133 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}") + +find_package(OpenGL REQUIRED) + +string(SUBSTRING ${CMAKE_SHARED_MODULE_SUFFIX} 1 -1 _clibext) +add_definitions(-DCMAKE_SHARED_MODULE_SUFFIX="${_clibext}") +unset(_clibext) + +set(RADIANTLIST + autosave.cpp autosave.h + brush.cpp brush.h + brush_primit.cpp brush_primit.h + brushmanip.cpp brushmanip.h + brushmodule.cpp brushmodule.h + brushnode.cpp brushnode.h + brushtokens.cpp brushtokens.h + brushxml.cpp brushxml.h + build.cpp build.h + camwindow.cpp camwindow.h + clippertool.cpp clippertool.h + commands.cpp commands.h + console.cpp console.h + csg.cpp csg.h + dialog.cpp dialog.h + eclass.cpp eclass.h + eclass_def.cpp eclass_def.h + eclass_doom3.cpp eclass_doom3.h + eclass_fgd.cpp eclass_fgd.h + eclass_xml.cpp eclass_xml.h + entity.cpp entity.h + entityinspector.cpp entityinspector.h + entitylist.cpp entitylist.h + environment.cpp environment.h + error.cpp error.h + feedback.cpp feedback.h + filetypes.cpp filetypes.h + filters.cpp filters.h + findtexturedialog.cpp findtexturedialog.h + glwidget.cpp glwidget.h + grid.cpp grid.h + groupdialog.cpp groupdialog.h + gtkdlgs.cpp gtkdlgs.h + gtkmisc.cpp gtkmisc.h + help.cpp help.h + image.cpp image.h + main.cpp main.h + mainframe.cpp mainframe.h + map.cpp map.h + mru.cpp mru.h + nullmodel.cpp nullmodel.h + parse.cpp parse.h + patch.cpp patch.h + patchdialog.cpp patchdialog.h + patchmanip.cpp patchmanip.h + patchmodule.cpp patchmodule.h + plugin.cpp plugin.h + pluginapi.cpp pluginapi.h + pluginmanager.cpp pluginmanager.h + pluginmenu.cpp pluginmenu.h + plugintoolbar.cpp plugintoolbar.h + points.cpp points.h + preferencedictionary.cpp preferencedictionary.h + preferences.cpp preferences.h + qe3.cpp qe3.h + qgl.cpp qgl.h + referencecache.cpp referencecache.h + renderer.cpp renderer.h + renderstate.cpp renderstate.h + resource.h + scenegraph.cpp scenegraph.h + select.cpp select.h + selection.cpp selection.h + server.cpp server.h + shaders.cpp shaders.h + sockets.cpp sockets.h + stacktrace.cpp stacktrace.h + surfacedialog.cpp surfacedialog.h + texmanip.cpp texmanip.h + textureentry.cpp textureentry.h + textures.cpp textures.h + texwindow.cpp texwindow.h + timer.cpp timer.h + treemodel.cpp treemodel.h + undo.cpp undo.h + url.cpp url.h + view.cpp view.h + watchbsp.cpp watchbsp.h + winding.cpp winding.h + windowobservers.cpp windowobservers.h + xmlstuff.cpp xmlstuff.h + xywindow.cpp xywindow.h + ) +if (WIN32) + list(APPEND RADIANTLIST multimon.cpp multimon.h) +endif () + +radiant_tool(worldspawn WIN32 worldspawn.rc ${RADIANTLIST}) +add_dependencies(worldspawn modules) +target_link_libraries(worldspawn + ${CMAKE_DL_LIBS} + ${LIBXML2_LIBRARIES} + ${OPENGL_gl_LIBRARY} + ${GTK${GTK_TARGET}_LIBRARIES} + ${GTKGL_LIBRARIES} + includes + cmdlib + container + ddslib + debugging + etclib + filematch + generic + l_net + math + mathlib + memory + modulesystem + os + picomodel + profile + script + signal + splines + stream + string + uilib + xmllib + ) +if (X11_LIBRARIES) + target_link_libraries(worldspawn ${X11_LIBRARIES}) +endif () + +copy_dlls(worldspawn) diff --git a/radiant/autosave.cpp b/radiant/autosave.cpp new file mode 100644 index 0000000..7528156 --- /dev/null +++ b/radiant/autosave.cpp @@ -0,0 +1,210 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "autosave.h" +#include "globaldefs.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 GDEF_OS_WINDOWS +#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(); + ui::alert(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) +{ + ui::CheckButton autosave_enabled = page.appendCheckBox("Autosave", "Enable Autosave", g_AutoSave_Enabled); + ui::SpinButton 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(makeCallbackF(Autosave_constructPage)); +} + + +#include "preferencesystem.h" +#include "stringio.h" + +void Autosave_Construct() +{ + GlobalPreferenceSystem().registerPreference("Autosave", make_property_string(g_AutoSave_Enabled)); + GlobalPreferenceSystem().registerPreference("AutosaveMinutes", make_property_string(m_AutoSave_Frequency)); + GlobalPreferenceSystem().registerPreference("Snapshots", make_property_string(g_SnapShots_Enabled)); + + Autosave_registerPreferencesPage(); +} + +void Autosave_Destroy() +{ +} diff --git a/radiant/autosave.h b/radiant/autosave.h new file mode 100644 index 0000000..e7dc00e --- /dev/null +++ b/radiant/autosave.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#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 0000000..57eb7af --- /dev/null +++ b/radiant/brush.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 + */ + +#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 = true; + +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 < faceVertices.size(); ++i) { + edgePairs[i].m_next = edgePairs.data() + absoluteIndex(next_edge(m_faces, faceVertices[i])); + } + } + + { + UniqueVertexBuffer 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; i < uniqueEdges.size(); ++i) { + FaceVertexId faceVertex = faceVertices[ProximalVertexArray_index(edgePairs, uniqueEdges[i])]; + m_edge_faces[i] = EdgeFaces(faceVertex.getFace(), + m_faces[faceVertex.getFace()]->getWinding()[faceVertex.getVertex()].adjacent); + } + } + + { + m_uniqueEdgePoints.resize(uniqueEdges.size()); + for (std::size_t i = 0; i < uniqueEdges.size(); ++i) { + FaceVertexId faceVertex = faceVertices[ProximalVertexArray_index(edgePairs, uniqueEdges[i])]; + + const Winding &w = m_faces[faceVertex.getFace()]->getWinding(); + 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 < faceVertices.size(); ++i) { + vertexRings[i].m_next = + vertexRings.data() + absoluteIndex(next_vertex(m_faces, faceVertices[i])); + } + } + + { + UniqueVertexBuffer 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; i < uniqueVertices.size(); ++i) { + FaceVertexId faceVertex = faceVertices[ProximalVertexArray_index(vertexRings, + uniqueVertices[i])]; + + const Winding &winding = m_faces[faceVertex.getFace()]->getWinding(); + 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; i < m_faces.size(); ++i) { + const Winding &winding = m_faces[i]->getWinding(); + 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; i < m_faces.size(); ++i) { + m_faces[i]->construct_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 0000000..7f0a180 --- /dev/null +++ b/radiant/brush.h @@ -0,0 +1,4064 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along 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 "mathlib.h" +#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); + +#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; +}; + +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([](FaceShaderObserver &observer) { + observer.realiseShader(); + }); + } + + void unrealise() + { + ASSERT_MESSAGE(m_realised, "FaceTexdef::unrealise: already unrealised"); + m_observers.forEach([](FaceShaderObserver &observer) { + observer.unrealiseShader(); + }); + 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()); + + if ( g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_HALFLIFE ) { + Vector3 s = m_projection.m_basis_s, t = m_projection.m_basis_t; + RotatePointAroundVector(m_projection.m_basis_s.data(), normal.data(), s.data(), m_projection.m_texdef.rotate); + RotatePointAroundVector(m_projection.m_basis_t.data(), normal.data(), t.data(), m_projection.m_texdef.rotate); + } + } +}; + +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); +// if (g_bp_globals.m_texdefTypeId != TEXDEFTYPEID_HALFLIFE) { +// 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(); + + if (g_brush_texturelock_enabled) { + Brush_textureChanged(); + } + } + + 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, bool ignorebasis) + { + undoSave(); + m_texdef.setTexdef(projection); + if (ignorebasis) + m_texdef.setBasis(m_plane.plane3().normal()); + 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 ¢roid() 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, bool selfIsLater) +{ + if (vector3_equal_epsilon(self.normal(), other.normal(), 0.001)) { + // same plane? prefer the one with smaller index + if (self.dist() == other.dist()) { + return selfIsLater; + } + 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; // bad idea, this overrides user setting + } else if (m_type == eBrushTypeHalfLife || m_type == eBrushTypeQuake3Valve) { + g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_HALFLIFE; + // g_brush_texturelock_enabled = true; // bad idea, this overrides user setting + } + + 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(), index < i)) { + 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 °en = 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 MemberCaller 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([&](const Vector3 &point) { + aabb_extend_by_point_safe(aabb, point); + }); + } + + void iterate_selected(RenderablePointVector &points) const + { + SelectedComponents_foreach([&](const Vector3 &point) { + const Colour4b colour_selected(0, 0, 255, 255); + points.push_back(pointvertex_for_windingpoint(point, colour_selected)); + }); + } + + 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 ¢roid() 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); + + // also draw a line indicating the direction of the cut + Vector3 lineverts[2]; + Winding_Centroid(m_winding, m_plane, lineverts[0]); + lineverts[1] = vector3_added(lineverts[0], vector3_scaled(m_plane.normal(), Brush::m_maxWorldCoord * 4)); + + glVertexPointer(3, GL_FLOAT, sizeof(Vector3), &lineverts[0]); + glDrawArrays(GL_LINES, 0, GLsizei(2)); + } + } + + 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 MemberCaller SelectedChangedCaller; + + void selectedChangedComponent(const Selectable &selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable); + GlobalSelectionSystem().onComponentSelection(*this, selectable); + } + + typedef MemberCaller 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 >(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 0000000..a7bae54 --- /dev/null +++ b/radiant/brush_primit.cpp @@ -0,0 +1,1401 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.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); + +#if GDEF_DEBUG + +void BPMatDump(float A[2][3]); + +#endif + +#if GDEF_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 ; i < w.numpoints ; i++ ) + { + x = vector3_dot( w.point_at( i ),texX ); + y = vector3_dot( w.point_at( i ),texY ); +#if 0 +#ifdef DBG_BP + if ( g_bp_globals.bNeedConvert ) { + // check we compute the same ST as the traditional texture computation used before + float S = f->brushprimit_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; i < f->face_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); + + 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 0000000..0420b4d --- /dev/null +++ b/radiant/brush_primit.h @@ -0,0 +1,163 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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; + +void ComputeAxisBase(const Vector3 &normal, Vector3 &texS, Vector3 &texT); + +#endif diff --git a/radiant/brushmanip.cpp b/radiant/brushmanip.cpp new file mode 100644 index 0000000..1493295 --- /dev/null +++ b/radiant/brushmanip.cpp @@ -0,0 +1,1372 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 +#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 < sides; ++i) { + double sv = sin(i * 3.14159265 * 2 / sides); + double cv = cos(i * 3.14159265 * 2 / sides); + + planepts[0][(axis + 1) % 3] = static_cast( 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 < sides; ++i) { + double sv = sin(i * 3.14159265 * 2 / sides); + double cv = cos(i * 3.14159265 * 2 / sides); + + planepts[0][0] = static_cast( 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 = 31; +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); + } + } +} + +const std::size_t c_brushRock_minSides = 10; +const std::size_t c_brushRock_maxSides = 1000; +const char *const c_brushRock_name = "brushRock"; + +void Brush_ConstructRock(Brush &brush, const AABB &bounds, std::size_t sides, const char *shader, + const TextureProjection &projection) +{ + if (sides < c_brushRock_minSides) { + globalErrorStream() << c_brushRock_name << ": sides " << Unsigned(sides) << ": too few sides, minimum is " + << Unsigned(c_brushRock_minSides) << "\n"; + return; + } + if (sides > c_brushRock_maxSides) { + globalErrorStream() << c_brushRock_name << ": sides " << Unsigned(sides) << ": too many sides, maximum is " + << Unsigned(c_brushRock_maxSides) << "\n"; + return; + } + + brush.clear(); + brush.reserve(sides * sides); + + float radius = max_extent(bounds.extents); + const Vector3 &mid = bounds.origin; + Vector3 planepts[3]; + + for (std::size_t j = 0; j < sides; j++) { + planepts[0][0] = rand() - (RAND_MAX / 2); + planepts[0][1] = rand() - (RAND_MAX / 2); + planepts[0][2] = rand() - (RAND_MAX / 2); + vector3_normalise(planepts[0]); + + // find two vectors that are perpendicular to planepts[0] + ComputeAxisBase(planepts[0], planepts[1], planepts[2]); + + planepts[0] = vector3_added(mid, vector3_scaled(planepts[0], radius)); + planepts[1] = vector3_added(planepts[0], vector3_scaled(planepts[1], radius)); + planepts[2] = vector3_added(planepts[0], vector3_scaled(planepts[2], radius)); + +#if 0 + // make sure the orientation is right + if ( vector3_dot( vector3_subtracted( planepts[0], mid ), vector3_cross( vector3_subtracted( planepts[1], mid ), vector3_subtracted( planepts[2], mid ) ) ) > 0 ) { + Vector3 h; + h = planepts[1]; + planepts[1] = planepts[2]; + planepts[2] = h; + globalOutputStream() << "flip\n"; + } + else{ + globalOutputStream() << "no flip\n"; + } +#endif + + 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; + case eBrushRock: { + StringOutputStream command; + command << c_brushRock_name << " -sides " << Unsigned(sides); + UndoableCommand undo(command.c_str()); + + Brush_ConstructRock(brush, bounds, sides, shader, projection); + } + break; + } +} + + +void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_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()); + } + } +} + + +void Scene_BrushSetTexdef_Selected(scene::Graph &graph, const TextureProjection &projection, bool ignorebasis) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.SetTexdef(projection, ignorebasis); + }); + SceneChangeNotify(); +} + +void Scene_BrushSetTexdef_Component_Selected(scene::Graph &graph, const TextureProjection &projection, bool ignorebasis) +{ + Scene_ForEachSelectedBrushFace(graph, [&](Face &face) { + face.SetTexdef(projection, ignorebasis); + }); + SceneChangeNotify(); +} + + +void Scene_BrushSetFlags_Selected(scene::Graph &graph, const ContentsFlagsValue &flags) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.SetFlags(flags); + }); + SceneChangeNotify(); +} + +void Scene_BrushSetFlags_Component_Selected(scene::Graph &graph, const ContentsFlagsValue &flags) +{ + Scene_ForEachSelectedBrushFace(graph, [&](Face &face) { + face.SetFlags(flags); + }); + SceneChangeNotify(); +} + +void Scene_BrushShiftTexdef_Selected(scene::Graph &graph, float s, float t) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.ShiftTexdef(s, t); + }); + SceneChangeNotify(); +} + +void Scene_BrushShiftTexdef_Component_Selected(scene::Graph &graph, float s, float t) +{ + Scene_ForEachSelectedBrushFace(graph, [&](Face &face) { + face.ShiftTexdef(s, t); + }); + SceneChangeNotify(); +} + +void Scene_BrushScaleTexdef_Selected(scene::Graph &graph, float s, float t) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.ScaleTexdef(s, t); + }); + SceneChangeNotify(); +} + +void Scene_BrushScaleTexdef_Component_Selected(scene::Graph &graph, float s, float t) +{ + Scene_ForEachSelectedBrushFace(graph, [&](Face &face) { + face.ScaleTexdef(s, t); + }); + SceneChangeNotify(); +} + +void Scene_BrushRotateTexdef_Selected(scene::Graph &graph, float angle) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.RotateTexdef(angle); + }); + SceneChangeNotify(); +} + +void Scene_BrushRotateTexdef_Component_Selected(scene::Graph &graph, float angle) +{ + Scene_ForEachSelectedBrushFace(graph, [&](Face &face) { + face.RotateTexdef(angle); + }); + SceneChangeNotify(); +} + + +void Scene_BrushSetShader_Selected(scene::Graph &graph, const char *name) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.SetShader(name); + }); + SceneChangeNotify(); +} + +void Scene_BrushSetShader_Component_Selected(scene::Graph &graph, const char *name) +{ + Scene_ForEachSelectedBrushFace(graph, [&](Face &face) { + face.SetShader(name); + }); + SceneChangeNotify(); +} + +void Scene_BrushSetDetail_Selected(scene::Graph &graph, bool detail) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.setDetail(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; +} + +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, [&](FaceInstance &faceinst) { + if (shader_equal(faceinst.getFace().GetShader(), find)) { + faceinst.setSelected(SelectionSystem::eFace, true); + } + }); + } else { + Scene_ForEachBrush_ForEachFace(graph, [&](Face &face) { Face_FindReplaceShader(face, find, replace); }); + } +} + +void Scene_BrushFindReplaceShader_Selected(scene::Graph &graph, const char *find, const char *replace) +{ + if (DoingSearch(replace)) { + Scene_ForEachSelectedBrush_ForEachFaceInstance(graph, [&](FaceInstance &faceinst) { + if (shader_equal(faceinst.getFace().GetShader(), find)) { + faceinst.setSelected(SelectionSystem::eFace, true); + } + }); + } else { + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + Face_FindReplaceShader(face, 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, [&](Face &face) { + Face_FindReplaceShader(face, find, replace); + }); + } +} + + +void Scene_BrushFitTexture_Selected(scene::Graph &graph, float s_repeat, float t_repeat) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + face.FitTexture(s_repeat, t_repeat); + }); + SceneChangeNotify(); +} + +void Scene_BrushFitTexture_Component_Selected(scene::Graph &graph, float s_repeat, float t_repeat) +{ + Scene_ForEachSelectedBrushFace(graph, [&](Face &face) { + face.FitTexture(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)); +} + +void Scene_BrushSelectByShader_Component(scene::Graph &graph, const char *name) +{ + Scene_ForEachSelectedBrush_ForEachFaceInstance(graph, [&](FaceInstance &face) { + printf("checking %s = %s\n", face.getFace().GetShader(), name); + if (shader_equal(face.getFace().GetShader(), name)) { + face.setSelected(SelectionSystem::eFace, true); + } + }); +} + +void Scene_BrushGetTexdef_Selected(scene::Graph &graph, TextureProjection &projection) +{ + bool done = false; + Scene_ForEachSelectedBrush_ForEachFace(graph, [&](Face &face) { + if (!done) { + done = true; + face.GetTexdef(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(); + } +} + + +void Scene_BrushGetFlags_Selected(scene::Graph &graph, ContentsFlagsValue &flags) +{ +#if 1 + if (GlobalSelectionSystem().countSelected() != 0) { + BrushInstance *brush = Instance_getBrush(GlobalSelectionSystem().ultimateSelected()); + if (brush != 0) { + bool done = false; + Brush_forEachFace(*brush, [&](Face &face) { + if (!done) { + done = true; + face.GetFlags(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 +} + + +void Scene_BrushGetShader_Selected(scene::Graph &graph, CopiedString &shader) +{ +#if 1 + if (GlobalSelectionSystem().countSelected() != 0) { + BrushInstance *brush = Instance_getBrush(GlobalSelectionSystem().ultimateSelected()); + if (brush != 0) { + bool done = false; + Brush_forEachFace(*brush, [&](Face &face) { + if (!done) { + done = true; + shader = face.GetShader(); + } + }); + } + } +#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 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 = false; + Brush_forEachFace(brush, [&](Face &face) { + if (m_filter->filter(face)) { + filtered = true; + } + }); + return filtered; + } +}; + +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 = true; + Brush_forEachFace(brush, [&](Face &face) { + if (!m_filter->filter(face)) { + filtered = false; + } + }); + 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_commonclip("textures/common/clip"); +filter_brush_all_faces g_filter_brush_commonclip(&g_filter_face_commonclip); + +filter_face_shader g_filter_face_fullclip("textures/common/fullclip"); +filter_brush_all_faces g_filter_brush_fullclip(&g_filter_face_fullclip); + +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); + +filter_face_shader_prefix g_filter_face_decals("textures/decals/"); +filter_brush_any_face g_filter_brush_decals(&g_filter_face_decals); + + +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_fullclip, EXCLUDE_CLIP); + add_brush_filter(g_filter_brush_commonclip, 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); + add_brush_filter(g_filter_brush_decals, EXCLUDE_DECALS); +} + +#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); +BrushPrefab g_brushrock(eBrushRock); + + +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; +ConstReferenceCaller &), PropertyImpl::Export> 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", makeCallbackF(Texdef_ToggleMoveLock), + 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("BrushRock", BrushPrefab::SetCaller(g_brushrock)); + + 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", makeCallbackF(ClipSelected), Accelerator(GDK_KEY_Return)); + GlobalCommands_insert("SplitSelected", makeCallbackF(SplitSelected), + Accelerator(GDK_KEY_Return, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("FlipClip", makeCallbackF(FlipClipper), + Accelerator(GDK_KEY_Return, (GdkModifierType) GDK_CONTROL_MASK)); + + GlobalCommands_insert("MakeDetail", makeCallbackF(Select_MakeDetail), + Accelerator('M', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("MakeStructural", makeCallbackF(Select_MakeStructural), + Accelerator('S', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); +} + +void Brush_constructMenu(ui::Menu 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"); + create_menu_item_with_mnemonic(menu, "Rock...", "BrushRock"); + menu_separator(menu); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "CSG"); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu_in_menu); + }*/ + create_menu_item_with_mnemonic(menu_in_menu, "Make _Hollow", "CSGMakeHollow"); + create_menu_item_with_mnemonic(menu_in_menu, "Make _Room", "CSGMakeRoom"); + 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); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Clipper"); + /*if (g_Layout_enableOpenStepUX.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_menu_item_with_mnemonic(menu, "Snap selection to _grid", "SnapToGrid"); + + 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 0000000..56f87fa --- /dev/null +++ b/radiant/brushmanip.h @@ -0,0 +1,111 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "string/stringfwd.h" +#include "generic/callback.h" + +enum EBrushPrefab { + eBrushCuboid, + eBrushPrism, + eBrushCone, + eBrushSphere, + eBrushRock, +}; + +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, bool ignorebasis); + +void Scene_BrushSetTexdef_Component_Selected(scene::Graph &graph, const TextureProjection &projection, bool ignorebasis); + +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); + +void Brush_constructMenu(ui::Menu 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 0000000..3b5d2f5 --- /dev/null +++ b/radiant/brushmodule.cpp @@ -0,0 +1,485 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" + +LatchedValue g_useAlternativeTextureProjection(false, "Use alternative texture-projection (\"brush primitives\")"); +static int g_numbrushtypes = 0; +static int g_selectedbrushtype; +static EBrushType g_brushTypes[8], g_brushType; +//bool g_showAlternativeTextureProjectionOption = false; +bool g_brush_always_caulk; + +bool getTextureLockEnabled() +{ + return g_brush_texturelock_enabled; +} + +struct Face_SnapPlanes { + static void Export(const QuantiseFunc &self, const Callback &returnz) + { + returnz(self == quantiseInteger); + } + + static void Import(QuantiseFunc &self, bool value) + { + self = value ? quantiseInteger : quantiseFloating; + } +}; + +const char* BrushType_getName( EBrushType type ){ + switch ( type ) + { + case eBrushTypeQuake: + case eBrushTypeQuake2: + case eBrushTypeQuake3: + return "Axial Projection"; + case eBrushTypeQuake3BP: + return "Brush Primitives"; + case eBrushTypeQuake3Valve: + case eBrushTypeHalfLife: + return "Valve 220"; + case eBrushTypeDoom3: + return "Doom3"; + case eBrushTypeQuake4: + return "Quake4"; + default: + return "unknown"; + } +} + +void Brush_constructPreferences(PreferencesPage &page) +{ + page.appendCheckBox( + "", "Snap planes to integer grid", + make_property(Face::m_quantise) + ); + page.appendEntry( + "Default texture scale", + g_texdef_default_scale + ); + if (g_numbrushtypes > 1) { + const char *names[8]; + for(int i = 0; i < g_numbrushtypes; i++) + names[i] = BrushType_getName(g_brushTypes[i]); + page.appendCombo("Default Brush Type", g_selectedbrushtype, StringArrayRange(names, names+g_numbrushtypes)); + } + // 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(makeCallbackF(Brush_constructPage)); +} + +void Brush_unlatchPreferences() +{ + Brush_toggleFormat(0); +} + +void Brush_setFormat(EBrushType t) +{ + if (g_numbrushtypes) { +// g_useAlternativeTextureProjection.m_value = g_useAlternativeTextureProjection.m_latched ^ i; + Brush::destroyStatic(); + g_brushType = t; + Brush::constructStatic(t); + } +} +void Brush_toggleFormat(int i) +{ + if (g_numbrushtypes) { +// g_useAlternativeTextureProjection.m_value = g_useAlternativeTextureProjection.m_latched ^ i; + Brush::destroyStatic(); + g_brushType = g_brushTypes[i]; + Brush::constructStatic(g_brushType); + } +} + +int Brush_toggleFormatCount() +{ + if (g_numbrushtypes) { + return g_numbrushtypes; + } + return 1; +} + +static void Brush_Construct(void) +{ + EBrushType type = g_brushType = g_brushTypes[g_selectedbrushtype]; + + // d1223m + GlobalPreferenceSystem().registerPreference( + "BrushAlwaysCaulk", + make_property_string(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", make_property_string(g_brush_texturelock_enabled)); + GlobalPreferenceSystem().registerPreference("BrushSnapPlanes", + make_property_string(Face::m_quantise)); + GlobalPreferenceSystem().registerPreference("TexdefDefaultScale", make_property_string(g_texdef_default_scale)); + + GridStatus_getTextureLockEnabled = getTextureLockEnabled; + g_texture_lock_status_changed = makeCallbackF(GridStatus_onTextureLockEnabledChanged); +} +static void Brush_Construct(EBrushType type0, EBrushType type1, EBrushType type2) +{ + g_numbrushtypes = 0; + g_brushTypes[g_numbrushtypes++] = type0; + if (type1 != type0) + g_brushTypes[g_numbrushtypes++] = type1; + if (type2 != type0 && type2 != type1) + g_brushTypes[g_numbrushtypes++] = type2; + + if (g_numbrushtypes) { + const char *value = g_pGameDescription->getKeyValue("brush_primit"); + if (!string_empty(value)) { + g_selectedbrushtype = atoi(value); + if (g_selectedbrushtype < 0 || g_selectedbrushtype >= g_numbrushtypes) + g_selectedbrushtype = 0; + } + +// GlobalPreferenceSystem().registerPreference( +// "BrushType", +// make_property(g_selectedbrushtype) +// ); + } + + Brush_Construct(); +} +static void Brush_Construct(EBrushType type0, EBrushType type1) +{ + Brush_Construct(type0, type1, type0); +} +static void Brush_Construct(EBrushType type0) +{ + Brush_Construct(type0, type0, type0); +} + +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 ConstReferenceCaller BrushFaceDataFromFaceCaller; +typedef Callback FaceCallback; + +class Quake3BrushCreator : public BrushCreator { +public: + scene::Node &createBrush() + { + return (new BrushNode)->node(); + } + + virtual EBrushType getBrushType() + { + return g_brushType; + } + virtual void setBrushType(EBrushType t) + { + if (g_brushType != t) + Brush_setFormat(t); + } + + 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, eBrushTypeQuake3BP, eBrushTypeQuake3Valve); + + 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, eBrushTypeHalfLife); + + 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 0000000..9501247 --- /dev/null +++ b/radiant/brushmodule.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_BRUSHMODULE_H ) +#define INCLUDED_BRUSHMODULE_H + +#include "brush.h" + +void Brush_clipperColourChanged(); + +void Brush_unlatchPreferences(); + +int Brush_toggleFormatCount(); + +void Brush_toggleFormat(int i); +void Brush_setFormat(EBrushType t); + +#endif diff --git a/radiant/brushnode.cpp b/radiant/brushnode.cpp new file mode 100644 index 0000000..aed252d --- /dev/null +++ b/radiant/brushnode.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 "brushnode.h" diff --git a/radiant/brushnode.h b/radiant/brushnode.h new file mode 100644 index 0000000..ad6b636 --- /dev/null +++ b/radiant/brushnode.h @@ -0,0 +1,182 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..3a3d970 --- /dev/null +++ b/radiant/brushtokens.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 "brushtokens.h" diff --git a/radiant/brushtokens.h b/radiant/brushtokens.h new file mode 100644 index 0000000..e467290 --- /dev/null +++ b/radiant/brushtokens.h @@ -0,0 +1,755 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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) +{ + //don't break if they're omitted. Yes, this might mean that it silently accepts quake-format maps where q3 maps were expected. + if (Tokeniser_nextTokenIsDigit(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_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 FaceTexdef_importTokens(FaceTexdef &texdef, Tokeniser &tokeniser) +{ +/* //pass it on to halflife parsing if we unexpectedly find it in one of the other formats (blame J.A.C.K.) + const char* token = tokeniser.getToken(); + if ( token && *token == '[' ) + { + tokeniser.ungetToken(); + texdef.m_scaleApplied = true; + return FaceTexdef_HalfLife_importTokens(texdef, tokeniser); + } + tokeniser.ungetToken(); +*/ + // 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 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 Quake3ValveFaceTokenImporter { + Face &m_face; +public: + Quake3ValveFaceTokenImporter(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)); + RETURN_FALSE_IF_FAIL(FaceShader_importContentsFlagsValue(m_face.getShader(), tokeniser)); + + 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 Quake3ValveFaceTokenExporter { + const Face &m_face; +public: + Quake3ValveFaceTokenExporter(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); + 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 eBrushTypeQuake3Valve: { + Quake3ValveFaceTokenImporter 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; + break; + case eBrushTypeQuake3Valve: { + Quake3ValveFaceTokenExporter 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 0000000..bca6877 --- /dev/null +++ b/radiant/brushxml.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 "brushxml.h" diff --git a/radiant/brushxml.h b/radiant/brushxml.h new file mode 100644 index 0000000..42c732a --- /dev/null +++ b/radiant/brushxml.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_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 0000000..4524a4e --- /dev/null +++ b/radiant/build.cpp @@ -0,0 +1,1104 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 +#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 ~Evaluatable() = default; + + 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() = default; + + 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; +const char *SEPARATOR_STRING = "-"; + +static bool is_separator(const BuildPair &p) +{ + if (!string_equal(p.first.c_str(), SEPARATOR_STRING)) { + return false; + } + for (Build::const_iterator j = p.second.begin(); j != p.second.end(); ++j) { + if (!string_equal((*j).c_str(), "")) { + return false; + } + } + return true; +} + + +typedef std::list Project; + +Project::iterator Project_find(Project &project, const char *name) +{ + return std::find_if(project.begin(), project.end(), [&](const BuildPair &self) { + return string_equal(self.first.c_str(), 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 if (string_equal(element.name(), "separator")) { + m_project.push_back(Project::value_type(SEPARATOR_STRING, Build())); + return *this; + } 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) { + if (is_separator(*i)) { + StaticElement buildElement("separator"); + importer.pushElement(buildElement); + importer.popElement(buildElement.name()); + importer << "\n"; + } else { + 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 "gtkutil/dialog.h" +#include "gtkutil/closure.h" +#include "gtkutil/window.h" +#include "gtkdlgs.h" + +void Build_refreshMenu(ui::Menu menu); + + +void BSPCommandList_Construct(ui::ListStore store, Project &project) +{ + store.clear(); + + for (Project::iterator i = project.begin(); i != project.end(); ++i) { + store.append(0, (*i).first.c_str()); + } + + store.append(); +} + +class ProjectList { +public: + Project &m_project; + ui::ListStore m_store{ui::null}; + bool m_changed; + + ProjectList(Project &project) : m_project(project), m_changed(false) + { + } +}; + +gboolean project_cell_edited(ui::CellRendererText cell, gchar *path_string, gchar *new_text, ProjectList *projectList) +{ + Project &project = projectList->m_project; + + auto path = ui::TreePath(path_string); + + ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length"); + + GtkTreeIter iter; + gtk_tree_model_get_iter(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); + gtk_list_store_remove(projectList->m_store, &iter); + } else { + (*i).first = new_text; + 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())); + + gtk_list_store_set(projectList->m_store, &iter, 0, new_text, -1); + projectList->m_store.append(); + } + + gtk_tree_path_free(path); + + Build_refreshMenu(g_bsp_menu); + + return FALSE; +} + +gboolean project_key_press(ui::TreeView widget, GdkEventKey *event, ProjectList *projectList) +{ + Project &project = projectList->m_project; + + if (event->keyval == GDK_KEY_Delete) { + auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(widget)); + GtkTreeIter iter; + GtkTreeModel *model; + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + auto 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(ui::TreeSelection selection, ui::ListStore store) +{ + Project &project = g_build_project; + + store.clear(); + + GtkTreeIter iter; + GtkTreeModel *model; + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + auto 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) { + store.append(0, (*i).c_str()); + } + store.append(); + } else { + g_current_build = 0; + } + } else { + g_current_build = 0; + } + + return FALSE; +} + +gboolean commands_cell_edited(ui::CellRendererText cell, gchar *path_string, gchar *new_text, ui::ListStore store) +{ + if (g_current_build == 0) { + return FALSE; + } + Build &build = *g_current_build; + + auto path = ui::TreePath(path_string); + + ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length"); + + GtkTreeIter iter; + gtk_tree_model_get_iter(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); + + store.append(); + } + + gtk_tree_path_free(path); + + Build_refreshMenu(g_bsp_menu); + + return FALSE; +} + +gboolean commands_key_press(ui::TreeView widget, GdkEventKey *event, ui::ListStore store) +{ + if (g_current_build == 0) { + return FALSE; + } + Build &build = *g_current_build; + + if (event->keyval == GDK_KEY_Delete) { + auto selection = gtk_tree_view_get_selection(widget); + GtkTreeIter iter; + GtkTreeModel *model; + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + auto 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; +} + + +ui::Window BuildMenuDialog_construct(ModalDialog &modal, ProjectList &projectList) +{ + ui::Window window = MainFrame_getWindow().create_dialog_window("Build Menu", G_CALLBACK(dialog_delete_callback), + &modal, -1, 400); + + { + auto table1 = create_dialog_table(2, 2, 4, 4, 4); + window.add(table1); + { + auto vbox = create_dialog_vbox(4); + table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL}); + { + auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &modal); + vbox.pack_start(button, FALSE, FALSE, 0); + } + { + auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &modal); + vbox.pack_start(button, FALSE, FALSE, 0); + } + } + auto buildViewStore = ui::ListStore::from(gtk_list_store_new(1, G_TYPE_STRING)); + auto buildView = ui::TreeView(ui::TreeModel::from(buildViewStore._handle)); + { + auto frame = create_dialog_frame("Build menu"); + table1.attach(frame, {0, 1, 0, 1}); + { + auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4); + frame.add(scr); + + { + auto view = buildView; + auto store = buildViewStore; + gtk_tree_view_set_headers_visible(view, FALSE); + + auto renderer = ui::CellRendererText(ui::New); + object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE); + renderer.connect("edited", G_CALLBACK(project_cell_edited), &projectList); + + auto column = ui::TreeViewColumn("", renderer, {{"text", 0}}); + gtk_tree_view_append_column(view, column); + + auto selection = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + + view.show(); + + projectList.m_store = store; + scr.add(view); + + view.connect("key_press_event", G_CALLBACK(project_key_press), &projectList); + + store.unref(); + } + } + } + { + auto frame = create_dialog_frame("Commandline"); + table1.attach(frame, {0, 1, 1, 2}); + { + auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4); + frame.add(scr); + + { + auto store = ui::ListStore::from(gtk_list_store_new(1, G_TYPE_STRING)); + + auto view = ui::TreeView(ui::TreeModel::from(store._handle)); + gtk_tree_view_set_headers_visible(view, FALSE); + + auto renderer = ui::CellRendererText(ui::New); + object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE); + renderer.connect("edited", G_CALLBACK(commands_cell_edited), store); + + auto column = ui::TreeViewColumn("", renderer, {{"text", 0}}); + gtk_tree_view_append_column(view, column); + + auto selection = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + + view.show(); + + scr.add(view); + + store.unref(); + + view.connect("key_press_event", G_CALLBACK(commands_key_press), store); + + auto sel = ui::TreeSelection::from(gtk_tree_view_get_selection(buildView)); + sel.connect("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); + + ui::Window 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; + } + + window.destroy(); +} + + +#include "gtkutil/menu.h" +#include "mainframe.h" +#include "preferences.h" +#include "qe3.h" + +class BuildMenuItem { + const char *m_name; +public: + ui::MenuItem m_item; + + BuildMenuItem(const char *name, ui::MenuItem item) + : m_name(name), m_item(item) + { + } + + void run() + { + RunBSP(m_name); + } + + typedef MemberCaller RunCaller; +}; + +typedef std::list BuildMenuItems; +BuildMenuItems g_BuildMenuItems; + + +ui::Menu g_bsp_menu{ui::null}; + +void Build_constructMenu(ui::Menu menu) +{ + for (Project::iterator i = g_build_project.begin(); i != g_build_project.end(); ++i) { + g_BuildMenuItems.push_back(BuildMenuItem((*i).first.c_str(), ui::MenuItem(ui::null))); + if (is_separator(*i)) { + g_BuildMenuItems.back().m_item = menu_separator(menu); + } else { + g_BuildMenuItems.back().m_item = create_menu_item_with_mnemonic(menu, (*i).first.c_str(), + BuildMenuItem::RunCaller( + g_BuildMenuItems.back())); + } + } +} + + +void Build_refreshMenu(ui::Menu menu) +{ + for (auto i = g_BuildMenuItems.begin(); i != g_BuildMenuItems.end(); ++i) { + menu.remove(ui::MenuItem(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", make_property_string(g_buildMenu)); + LoadBuildMenu(); +} + +void BuildMenu_Destroy() +{ + SaveBuildMenu(); +} diff --git a/radiant/build.h b/radiant/build.h new file mode 100644 index 0000000..3419f0a --- /dev/null +++ b/radiant/build.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 + */ +#include + +#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(); + +void Build_constructMenu(ui::Menu menu); + +extern ui::Menu g_bsp_menu; + + +#endif diff --git a/radiant/camwindow.cpp b/radiant/camwindow.cpp new file mode 100644 index 0000000..abfc052 --- /dev/null +++ b/radiant/camwindow.cpp @@ -0,0 +1,2204 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 +#include + +#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; + bool m_showLighting; + bool m_showAlpha; + int m_nStrafeMode; + + camwindow_globals_private_t() : + m_nMoveSpeed(50), + m_bCamLinkSpeed(true), + m_nAngleSpeed(3), + m_bCamInverseMouse(false), + m_bCamDiscrete(true), + m_bCubicClipping(true), + m_showStats(false), + m_showLighting(false), + m_showAlpha(true), + m_nStrafeMode(0) + { + } + +}; + +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(100.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 + { +#if 1 + const float dtime = 0.05f; +#else + float dtime = camera.m_keycontrol_timer.elapsed_msec() / static_cast( msec_per_sec ); + dtime *= 0.01f; +#endif + + if (g_camwindow_globals_private.m_bCamInverseMouse) { + camera.angles[CAMERA_PITCH] -= (dy * g_camwindow_globals_private.m_nAngleSpeed) * dtime; + } else { + camera.angles[CAMERA_PITCH] += (dy * g_camwindow_globals_private.m_nAngleSpeed) * dtime; + } + + camera.angles[CAMERA_YAW] += (dx * g_camwindow_globals_private.m_nAngleSpeed) * dtime; + + 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) +{ + float xf = (float) (x - camera.width / 2) / (camera.width / 2); + float yf = (float) (y - camera.height / 2) / (camera.height / 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 * g_camwindow_globals_private.m_nAngleSpeed) * dtime; + } + if (camera.movementflags & MOVE_ROTRIGHT) { + camera.angles[CAMERA_YAW] -= (15 * g_camwindow_globals_private.m_nAngleSpeed) * dtime; + } + if (camera.movementflags & MOVE_PITCHUP) { + camera.angles[CAMERA_PITCH] += (15 * g_camwindow_globals_private.m_nAngleSpeed) * dtime; + if (camera.angles[CAMERA_PITCH] > 90) { + camera.angles[CAMERA_PITCH] = 90; + } + } + if (camera.movementflags & MOVE_PITCHDOWN) { + camera.angles[CAMERA_PITCH] -= (15 * g_camwindow_globals_private.m_nAngleSpeed) * dtime; + 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 ) * 0.01; + camera.m_keycontrol_timer.start(); + Cam_KeyControl(camera, 0.016f); + + 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; + + +const float SPEED_MOVE = 32; +const float SPEED_TURN = 22.5; +const float MIN_CAM_SPEED = 10; +const float MAX_CAM_SPEED = 610; +const float 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); + + switch (g_camwindow_globals_private.m_nStrafeMode) { + case 0: + cam->m_strafe = (state & GDK_SHIFT_MASK) != 0; + if (cam->m_strafe) { + cam->m_strafe_forward = (state & GDK_CONTROL_MASK) != 0; + } else { + cam->m_strafe_forward = false; + } + break; + case 1: + cam->m_strafe = (state & GDK_CONTROL_MASK) != 0 && (state & GDK_SHIFT_MASK) == 0; + cam->m_strafe_forward = false; + break; + case 2: + cam->m_strafe = (state & GDK_CONTROL_MASK) != 0 && (state & GDK_SHIFT_MASK) == 0; + cam->m_strafe_forward = cam->m_strafe; + break; + } +} + +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: + ui::GLArea m_gl_widget; + ui::Window m_parent{ui::null}; + + 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_freelook_key_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; +} + + +ui::GLArea CamWnd_getWidget(CamWnd &camwnd) +{ + return camwnd.m_gl_widget; +} + +ui::Window CamWnd_getParent(CamWnd &camwnd) +{ + return camwnd.m_parent; +} + +ToggleShown g_camera_shown(true); + +void CamWnd_setParent(CamWnd &camwnd, ui::Window parent) +{ + camwnd.m_parent = parent; + g_camera_shown.connect(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(ui::Widget 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(ui::Widget widget, GdkEventButton *event, CamWnd *camwnd) +{ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + camwnd->DisableFreeMove(); + return TRUE; + } + return FALSE; +} + +static gint disable_freelook_key_press(ui::Entry widget, GdkEventKey *event, CamWnd *camwnd) +{ + if (event->keyval == GDK_KEY_Escape) { + camwnd->DisableFreeMove(); + return TRUE; + } + return FALSE; +} + +#if 0 + gboolean mousecontrol_button_press( ui::Widget 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 (self.m_gl_widget.visible()) { + self.m_XORRectangle.set( + rectangle_from_area(area.min, area.max, self.getCamera().width, self.getCamera().height)); + } +} + + +gboolean selection_button_press(ui::Widget 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(ui::Widget 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(ui::Widget widget) +{ + auto allocation = widget.dimensions(); + return WindowVector(static_cast( allocation.width / 2 ), static_cast(allocation.height / 2 )); +} + +gboolean selection_button_press_freemove(ui::Widget 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(ui::Widget 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(ui::Widget widget, GdkEventMotion *event, WindowObserver *observer) +{ + observer->onMouseMotion(windowvector_for_widget_centre(widget), modifiers_for_state(event->state)); + return FALSE; +} + +gboolean wheelmove_scroll(ui::Widget 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(ui::Widget 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(ui::Widget 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_KEY_Up), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraBack", Accelerator(GDK_KEY_Down), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraLeft", Accelerator(GDK_KEY_Left), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraRight", Accelerator(GDK_KEY_Right), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraStrafeRight", Accelerator(GDK_KEY_period), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraStrafeLeft", Accelerator(GDK_KEY_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_KEY_Up), + FreeMoveCameraMoveForwardKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveForwardKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveBack", Accelerator(GDK_KEY_Down), + FreeMoveCameraMoveBackKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveBackKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveLeft", Accelerator(GDK_KEY_Left), + FreeMoveCameraMoveLeftKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveLeftKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveRight", Accelerator(GDK_KEY_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_KEY_Up)); + GlobalCommands_insert("CameraBack", ReferenceCaller(camwnd.getCamera()), + Accelerator(GDK_KEY_Down)); + GlobalCommands_insert("CameraLeft", + ReferenceCaller(camwnd.getCamera()), + Accelerator(GDK_KEY_Left)); + GlobalCommands_insert("CameraRight", + ReferenceCaller(camwnd.getCamera()), + Accelerator(GDK_KEY_Right)); + GlobalCommands_insert("CameraStrafeRight", + ReferenceCaller(camwnd.getCamera()), + Accelerator(GDK_KEY_period)); + GlobalCommands_insert("CameraStrafeLeft", + ReferenceCaller(camwnd.getCamera()), + Accelerator(GDK_KEY_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"); +} + +struct CamWnd_Move_Discrete { + static void Export(const Callback &returnz) + { + returnz(g_camwindow_globals_private.m_bCamDiscrete); + } + + static void Import(bool value) + { + if (g_camwnd) { + Import_(*g_camwnd, value); + } else { + g_camwindow_globals_private.m_bCamDiscrete = value; + } + } + + static void 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_Add_Handlers_Move(CamWnd &camwnd) +{ + camwnd.m_selection_button_press_handler = camwnd.m_gl_widget.connect("button_press_event", + G_CALLBACK(selection_button_press), + camwnd.m_window_observer); + camwnd.m_selection_button_release_handler = camwnd.m_gl_widget.connect("button_release_event", + G_CALLBACK(selection_button_release), + camwnd.m_window_observer); + camwnd.m_selection_motion_handler = camwnd.m_gl_widget.connect("motion_notify_event", + G_CALLBACK(DeferredMotion::gtk_motion), + &camwnd.m_deferred_motion); + + camwnd.m_freelook_button_press_handler = camwnd.m_gl_widget.connect("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); + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_freelook_key_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 = camwnd.m_gl_widget.connect( + "button_press_event", + G_CALLBACK(selection_button_press_freemove), + camwnd.m_window_observer); + camwnd.m_selection_button_release_handler = camwnd.m_gl_widget.connect( + "button_release_event", + G_CALLBACK(selection_button_release_freemove), + camwnd.m_window_observer); + camwnd.m_selection_motion_handler = camwnd.m_gl_widget.connect( + "motion_notify_event", + G_CALLBACK(selection_motion_freemove), + camwnd.m_window_observer); + camwnd.m_freelook_button_press_handler = camwnd.m_gl_widget.connect( + "button_press_event", + G_CALLBACK(disable_freelook_button_press), + &camwnd); + camwnd.m_freelook_key_press_handler = camwnd.m_gl_widget.connect( + "key_press_event", + G_CALLBACK(disable_freelook_key_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); + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_freelook_key_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_freelook_key_press_handler(0), + m_drawing(false) +{ + m_bFreeMove = false; + + GlobalWindowObservers_add(m_window_observer); + GlobalWindowObservers_connectWidget(m_gl_widget); + + m_window_observer->setRectangleDrawCallback( + ReferenceCaller(*this)); + m_window_observer->setView(m_view); + + g_object_ref(m_gl_widget._handle); + + 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_can_focus(m_gl_widget, true); + + m_sizeHandler = m_gl_widget.connect("size_allocate", G_CALLBACK(camera_size_allocate), this); + m_exposeHandler = m_gl_widget.on_render(G_CALLBACK(camera_expose), this); + + Map_addValidCallback(g_map, DeferredDrawOnMapValidChangedCaller(m_deferredDraw)); + + CamWnd_registerCommands(*this); + + CamWnd_Add_Handlers_Move(*this); + + m_gl_widget.connect("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); + + m_gl_widget.unref(); + + 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( 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( 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(ui::Widget 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 = m_gl_widget.connect("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"); + if (m_bFreeMove == false) { + return; + } + 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; + } + } + + 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; +} +void ShowStatsExport(const Callback &importer) +{ + importer(g_camwindow_globals_private.m_showStats); +} +FreeCaller &), ShowStatsExport> g_show_stats_caller; +Callback &)> g_show_stats_callback(g_show_stats_caller); +ToggleItem g_show_stats(g_show_stats_callback); + +void ShowLightToggle() +{ + g_camwindow_globals_private.m_showLighting ^= 1; +} +void ShowLightExport(const Callback &importer) +{ + importer(g_camwindow_globals_private.m_showLighting); +} +FreeCaller &), ShowLightExport> g_show_light_caller; +Callback &)> g_show_light_callback(g_show_light_caller); +ToggleItem g_show_light(g_show_light_callback); + +void ShowAlphaToggle() +{ + g_camwindow_globals_private.m_showAlpha ^= 1; +} +void ShowAlphaExport(const Callback &importer) +{ + importer(g_camwindow_globals_private.m_showAlpha); +} +FreeCaller &), ShowAlphaExport> g_show_alpha_caller; +Callback &)> g_show_alpha_callback(g_show_alpha_caller); +ToggleItem g_show_alpha(g_show_alpha_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); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + 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 )); + + + /* | RENDER_POLYGONSMOOTH | RENDER_LINESMOOTH */ + unsigned int globalstate = + /*RENDER_DEPTHTEST | RENDER_COLOURWRITE | | + RENDER_COLOURARRAY | RENDER_OFFSETLINE | RENDER_FOG | RENDER_COLOURCHANGE |*/ + RENDER_DEPTHTEST | RENDER_CULLFACE | RENDER_POLYOFS | RENDER_DEPTHWRITE; + + if (g_camwindow_globals_private.m_showAlpha) { + globalstate |= RENDER_ALPHATEST | RENDER_BLEND; + } + + if (g_camwindow_globals_private.m_showLighting) { + GLfloat inverse_cam_dir[4], ambient[4], diffuse[4]; + ambient[0] = ambient[1] = ambient[2] = 0.25f; + ambient[3] = 1.0f; + diffuse[0] = diffuse[1] = diffuse[2] = 1.0f; + diffuse[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); + globalstate |= RENDER_LIGHTING; + } + + switch (m_Camera.draw_mode) { + case cd_wire: + break; + case cd_solid: + globalstate |= RENDER_FILL + | RENDER_SCALED; + break; + case cd_texture: + globalstate |= RENDER_FILL + | RENDER_TEXTURE + | RENDER_SMOOTH + | RENDER_SCALED; + break; + case cd_lighting: + globalstate |= RENDER_FILL + | 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; + } + + /* render */ + { + 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 ) - GlobalOpenGL().m_font->getPixelDescent(), 0.0f); + extern const char *Renderer_GetStats(); + GlobalOpenGL().drawString(Renderer_GetStats()); + + glRasterPos3f(1.0f, static_cast( m_Camera.height ) - GlobalOpenGL().m_font->getPixelDescent() - + GlobalOpenGL().m_font->getPixelHeight(), 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; + + if (glwidget_make_current(m_gl_widget) != FALSE) { + if (Map_Valid(g_map) && ScreenUpdates_Enabled()) { + GlobalOpenGL_debugAssertNoErrors(); + Cam_Draw(); + GlobalOpenGL_debugAssertNoErrors(); + 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(ui::Menu 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; +} + +ConstReferenceCaller &), PropertyImpl::Export> 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); +} + +struct Camera_FarClip { + static void Export(const Callback &returnz) + { + returnz(g_camwindow_globals_private.m_bCubicClipping); + } + + static void Import(bool value) + { + Camera_SetFarClip(value); + } +}; + +void Camera_ToggleFarClip() +{ + Camera_SetFarClip(!Camera_GetFarClip()); +} + + +void CamWnd_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_toggle_button(toolbar, "Cubic clip the camera view (\\)", "view_cubicclipping.xpm", + "ToggleCubicClip"); +} + +void CamWnd_registerShortcuts() +{ + toggle_add_accelerator("ToggleCubicClip"); + 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) +{ + // 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); +} + +struct RenderMode { + static void Export(const Callback &returnz) + { + switch (CamWnd_GetMode()) { + case cd_wire: + returnz(0); + break; + case cd_solid: + returnz(1); + break; + case cd_texture: + returnz(2); + break; + case cd_lighting: + returnz(3); + break; + } + } + + static void Import(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); + } + } +}; + +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); + 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); + page.appendCheckBox("", "Invert mouse vertical axis", g_camwindow_globals_private.m_bCamInverseMouse); + page.appendCheckBox( + "", "Discrete movement", + make_property() + ); + page.appendCheckBox( + "", "Enable far-clip plane", + make_property() + ); + + + const char *render_mode[] = {"Wireframe", "Flatshade", "Textured"}; + + page.appendCombo( + "Render Mode", + STRING_ARRAY_RANGE(render_mode), + make_property() + ); + + + const char *strafe_mode[] = {"Both", "Forward", "Up"}; + + page.appendCombo( + "Strafe Mode", + g_camwindow_globals_private.m_nStrafeMode, + STRING_ARRAY_RANGE(strafe_mode) + ); +} + +void Camera_constructPage(PreferenceGroup &group) +{ + PreferencesPage page(group.createPage("Camera", "Camera View Preferences")); + Camera_constructPreferences(page); +} + +void Camera_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(makeCallbackF(Camera_constructPage)); +} + +#include "preferencesystem.h" +#include "stringio.h" +#include "dialog.h" + +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", makeCallbackF(GlobalCamera_ResetAngles), Accelerator(GDK_KEY_End)); + + GlobalToggles_insert("ToggleCubicClip", makeCallbackF(Camera_ToggleFarClip), + ToggleItem::AddCallbackCaller(g_getfarclip_item), + Accelerator('\\', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("CubicClipZoomIn", makeCallbackF(Camera_CubeIn), + Accelerator('[', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("CubicClipZoomOut", makeCallbackF(Camera_CubeOut), + Accelerator(']', (GdkModifierType) GDK_CONTROL_MASK)); + + GlobalCommands_insert("UpFloor", makeCallbackF(Camera_ChangeFloorUp), Accelerator(GDK_KEY_Prior)); + GlobalCommands_insert("DownFloor", makeCallbackF(Camera_ChangeFloorDown), Accelerator(GDK_KEY_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", makeCallbackF(GlobalCamera_LookThroughSelected)); + GlobalCommands_insert("LookThroughCamera", makeCallbackF(GlobalCamera_LookThroughCamera)); + + GlobalCommands_insert("CameraSpeedInc", makeCallbackF(CameraSpeed_increase), + Accelerator(GDK_KEY_KP_Add, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("CameraSpeedDec", makeCallbackF(CameraSpeed_decrease), + Accelerator(GDK_KEY_KP_Subtract, (GdkModifierType) GDK_SHIFT_MASK)); + + GlobalShortcuts_insert("CameraForward", Accelerator(GDK_KEY_Up)); + GlobalShortcuts_insert("CameraBack", Accelerator(GDK_KEY_Down)); + GlobalShortcuts_insert("CameraLeft", Accelerator(GDK_KEY_Left)); + GlobalShortcuts_insert("CameraRight", Accelerator(GDK_KEY_Right)); + GlobalShortcuts_insert("CameraStrafeRight", Accelerator(GDK_KEY_period)); + GlobalShortcuts_insert("CameraStrafeLeft", Accelerator(GDK_KEY_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_KEY_Up)); + GlobalShortcuts_insert("CameraFreeMoveBack", Accelerator(GDK_KEY_Down)); + GlobalShortcuts_insert("CameraFreeMoveLeft", Accelerator(GDK_KEY_Left)); + GlobalShortcuts_insert("CameraFreeMoveRight", Accelerator(GDK_KEY_Right)); + + GlobalToggles_insert("ShowStats", makeCallbackF(ShowStatsToggle), ToggleItem::AddCallbackCaller(g_show_stats)); + GlobalToggles_insert("ShowLighting", makeCallbackF(ShowLightToggle), ToggleItem::AddCallbackCaller(g_show_light)); + GlobalToggles_insert("ShowAlpha", makeCallbackF(ShowAlphaToggle), ToggleItem::AddCallbackCaller(g_show_alpha)); + + GlobalPreferenceSystem().registerPreference("ShowStats", + make_property_string(g_camwindow_globals_private.m_showStats)); + GlobalPreferenceSystem().registerPreference("ShowLighting", + make_property_string(g_camwindow_globals_private.m_showLighting)); + GlobalPreferenceSystem().registerPreference("ShowAlpha", + make_property_string(g_camwindow_globals_private.m_showAlpha)); + GlobalPreferenceSystem().registerPreference("MoveSpeed", + make_property_string(g_camwindow_globals_private.m_nMoveSpeed)); + GlobalPreferenceSystem().registerPreference("CamLinkSpeed", + make_property_string(g_camwindow_globals_private.m_bCamLinkSpeed)); + GlobalPreferenceSystem().registerPreference("AngleSpeed", + make_property_string(g_camwindow_globals_private.m_nAngleSpeed)); + GlobalPreferenceSystem().registerPreference("CamInverseMouse", + make_property_string(g_camwindow_globals_private.m_bCamInverseMouse)); + GlobalPreferenceSystem().registerPreference("CamDiscrete", make_property_string()); + GlobalPreferenceSystem().registerPreference("CubicClipping", + make_property_string(g_camwindow_globals_private.m_bCubicClipping)); + GlobalPreferenceSystem().registerPreference("CubicScale", make_property_string(g_camwindow_globals.m_nCubicScale)); + GlobalPreferenceSystem().registerPreference("SI_Colors4", + make_property_string(g_camwindow_globals.color_cameraback)); + GlobalPreferenceSystem().registerPreference("SI_Colors12", + make_property_string(g_camwindow_globals.color_selbrushes3d)); + GlobalPreferenceSystem().registerPreference("CameraRenderMode", make_property_string()); + GlobalPreferenceSystem().registerPreference("StrafeMode", + make_property_string(g_camwindow_globals_private.m_nStrafeMode)); + + CamWnd_constructStatic(); + + Camera_registerPreferencesPage(); +} + +void CamWnd_Destroy() +{ + CamWnd_destroyStatic(); +} + +void CamWnd_DisableMovement() +{ + g_camwnd->DisableFreeMove(); +} diff --git a/radiant/camwindow.h b/radiant/camwindow.h new file mode 100644 index 0000000..4b76f42 --- /dev/null +++ b/radiant/camwindow.h @@ -0,0 +1,89 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "math/vector.h" +#include "signal/signalfwd.h" + +class CamWnd; + +CamWnd *NewCamWnd(); + +void DeleteCamWnd(CamWnd *camwnd); + +void AddCameraMovedCallback(const SignalHandler &handler); + +void CamWnd_Update(CamWnd &camwnd); + +ui::GLArea CamWnd_getWidget(CamWnd &camwnd); + +void CamWnd_setParent(CamWnd &camwnd, ui::Window parent); + +void GlobalCamera_setCamWnd(CamWnd &camwnd); + +void fill_view_camera_menu(ui::Menu menu); + +void CamWnd_constructToolbar(ui::Toolbar 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.15f, 0.15f, 0.15f), + color_selbrushes3d(1.0f, 0.0f, 0.0f), + 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 0000000..c167716 --- /dev/null +++ b/radiant/clippertool.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 "clippertool.h" diff --git a/radiant/clippertool.h b/radiant/clippertool.h new file mode 100644 index 0000000..5c1365d --- /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 0000000..e5448bc --- /dev/null +++ b/radiant/commands.cpp @@ -0,0 +1,634 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "gtk/gtk.h" +#include "debugging/debugging.h" +#include "warnings.h" + +#include +#include "string/string.h" +#include "versionlib.h" +#include "gtkutil/messagebox.h" +#include "gtkmisc.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, int type) +{ + Shortcuts::iterator i = g_shortcuts.find(name); + if (i != g_shortcuts.end()) { + (*i).second.second = type; + } +} + +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 Callback &)> &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; +} + + +void disconnect_accelerator(const char *name) +{ + Shortcuts::iterator i = g_shortcuts.find(name); + if (i != g_shortcuts.end()) { + switch ((*i).second.second) { + case 1: + // command + command_disconnect_accelerator(name); + break; + case 2: + // toggle + toggle_remove_accelerator(name); + break; + } + } +} + +void connect_accelerator(const char *name) +{ + Shortcuts::iterator i = g_shortcuts.find(name); + if (i != g_shortcuts.end()) { + switch ((*i).second.second) { + case 1: + // command + command_connect_accelerator(name); + break; + case 2: + // toggle + toggle_add_accelerator(name); + break; + } + } +} + + +#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), m_list(ui::null), m_command_iter(), m_model(ui::null), + m_waiting_for_key(false) + { + } + + ModalDialogButton m_close_button; + + ui::TreeView m_list; + GtkTreeIter m_command_iter; + ui::TreeModel m_model; + bool m_waiting_for_key; +}; + +void accelerator_clear_button_clicked(ui::Button btn, gpointer dialogptr) +{ + command_list_dialog_t &dialog = *(command_list_dialog_t *) dialogptr; + + if (dialog.m_waiting_for_key) { + // just unhighlight, user wanted to cancel + dialog.m_waiting_for_key = false; + gtk_list_store_set(ui::ListStore::from(dialog.m_model), &dialog.m_command_iter, 2, false, -1); + gtk_widget_set_sensitive(dialog.m_list, true); + dialog.m_model = ui::TreeModel(ui::null); + return; + } + + auto sel = gtk_tree_view_get_selection(dialog.m_list); + GtkTreeModel *model; + GtkTreeIter iter; + if (!gtk_tree_selection_get_selected(sel, &model, &iter)) { + return; + } + + GValue val; + memset(&val, 0, sizeof(val)); + gtk_tree_model_get_value(model, &iter, 0, &val); + const char *commandName = g_value_get_string(&val);; + + // clear the ACTUAL accelerator too! + disconnect_accelerator(commandName); + + Shortcuts::iterator thisShortcutIterator = g_shortcuts.find(commandName); + if (thisShortcutIterator == g_shortcuts.end()) { + return; + } + thisShortcutIterator->second.first = accelerator_null(); + + gtk_list_store_set(ui::ListStore::from(model), &iter, 1, "", -1); + + g_value_unset(&val); +} + +void accelerator_edit_button_clicked(ui::Button btn, gpointer dialogptr) +{ + command_list_dialog_t &dialog = *(command_list_dialog_t *) dialogptr; + + // 1. find selected row + auto sel = gtk_tree_view_get_selection(dialog.m_list); + GtkTreeModel *model; + GtkTreeIter iter; + if (!gtk_tree_selection_get_selected(sel, &model, &iter)) { + return; + } + dialog.m_command_iter = iter; + dialog.m_model = ui::TreeModel::from(model); + + // 2. disallow changing the row + //gtk_widget_set_sensitive(dialog.m_list, false); + + // 3. highlight the row + gtk_list_store_set(ui::ListStore::from(model), &iter, 2, true, -1); + + // 4. grab keyboard focus + dialog.m_waiting_for_key = true; +} + +bool accelerator_window_key_press(ui::Window widget, GdkEventKey *event, gpointer dialogptr) +{ + command_list_dialog_t &dialog = *(command_list_dialog_t *) dialogptr; + + if (!dialog.m_waiting_for_key) { + return false; + } + +#if 0 + if ( event->is_modifier ) { + return false; + } +#else + switch (event->keyval) { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Caps_Lock: + case GDK_KEY_Shift_Lock: + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Super_L: + case GDK_KEY_Super_R: + case GDK_KEY_Hyper_L: + case GDK_KEY_Hyper_R: + return false; + } +#endif + + dialog.m_waiting_for_key = false; + + // 7. find the name of the accelerator + GValue val; + memset(&val, 0, sizeof(val)); + gtk_tree_model_get_value(dialog.m_model, &dialog.m_command_iter, 0, &val); + const char *commandName = g_value_get_string(&val);; + Shortcuts::iterator thisShortcutIterator = g_shortcuts.find(commandName); + if (thisShortcutIterator == g_shortcuts.end()) { + gtk_list_store_set(ui::ListStore::from(dialog.m_model), &dialog.m_command_iter, 2, false, -1); + gtk_widget_set_sensitive(dialog.m_list, true); + return true; + } + + // 8. build an Accelerator + Accelerator newAccel(event->keyval, (GdkModifierType) event->state); + + // 8. verify the key is still free, show a dialog to ask what to do if not + class VerifyAcceleratorNotTaken : public CommandVisitor { + const char *commandName; + const Accelerator &newAccel; + ui::Widget widget; + ui::TreeModel model; + public: + bool allow; + + VerifyAcceleratorNotTaken(const char *name, const Accelerator &accelerator, ui::Widget w, ui::TreeModel m) + : commandName(name), newAccel(accelerator), widget(w), model(m), allow(true) + { + } + + void visit(const char *name, Accelerator &accelerator) + { + if (!strcmp(name, commandName)) { + return; + } + if (!allow) { + return; + } + if (accelerator.key == 0) { + return; + } + if (accelerator == newAccel) { + StringOutputStream msg; + msg << "The command " << name << " is already assigned to the key " << accelerator << ".\n\n" + << "Do you want to unassign " << name << " first?"; + auto r = ui::alert(widget.window(), msg.c_str(), "Key already used", ui::alert_type::YESNOCANCEL); + if (r == ui::alert_response::YES) { + // clear the ACTUAL accelerator too! + disconnect_accelerator(name); + // delete the modifier + accelerator = accelerator_null(); + // empty the cell of the key binds dialog + GtkTreeIter i; + if (gtk_tree_model_get_iter_first(model, &i)) { + for (;;) { + GValue val; + memset(&val, 0, sizeof(val)); + gtk_tree_model_get_value(model, &i, 0, &val); + const char *thisName = g_value_get_string(&val);; + if (!strcmp(thisName, name)) { + gtk_list_store_set(ui::ListStore::from(model), &i, 1, "", -1); + } + g_value_unset(&val); + if (!gtk_tree_model_iter_next(model, &i)) { + break; + } + } + } + } else if (r == ui::alert_response::CANCEL) { + // aborted + allow = false; + } + } + } + } verify_visitor(commandName, newAccel, widget, dialog.m_model); + GlobalShortcuts_foreach(verify_visitor); + + gtk_list_store_set(ui::ListStore::from(dialog.m_model), &dialog.m_command_iter, 2, false, -1); + gtk_widget_set_sensitive(dialog.m_list, true); + + if (verify_visitor.allow) { + // clear the ACTUAL accelerator first + disconnect_accelerator(commandName); + + thisShortcutIterator->second.first = newAccel; + + // write into the cell + StringOutputStream modifiers; + modifiers << newAccel; + gtk_list_store_set(ui::ListStore::from(dialog.m_model), &dialog.m_command_iter, 1, modifiers.c_str(), -1); + + // set the ACTUAL accelerator too! + connect_accelerator(commandName); + } + + g_value_unset(&val); + + dialog.m_model = ui::TreeModel(ui::null); + + return true; +} + +/* + GtkTreeIter row; + GValue val; + if(!model) {g_error("Unable to get model from cell renderer");} + gtk_tree_model_get_iter_from_string(model, &row, path_string); + + gtk_tree_model_get_value(model, &row, 0, &val); + const char *name = g_value_get_string(&val); + Shortcuts::iterator i = g_shortcuts.find(name); + if(i != g_shortcuts.end()) + { + accelerator_parse(i->second.first, new_text); + StringOutputStream modifiers; + modifiers << i->second.first; + gtk_list_store_set(ui::ListStore::from(model), &row, 1, modifiers.c_str(), -1); + } + }; + */ + +void DoCommandListDlg() +{ + command_list_dialog_t dialog; + + ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Mapped Commands", dialog, -1, 400); + window.on_key_press([](ui::Widget widget, GdkEventKey *event, gpointer dialogptr) { + return accelerator_window_key_press(ui::Window::from(widget), event, dialogptr); + }, &dialog); + + auto accel = ui::AccelGroup(ui::New); + window.add_accel_group(accel); + + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + + { + auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC); + hbox.pack_start(scr, TRUE, TRUE, 0); + + { + auto store = ui::ListStore::from( + gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT)); + + auto view = ui::TreeView(ui::TreeModel::from(store._handle)); + dialog.m_list = view; + + gtk_tree_view_set_enable_search(view, false); // annoying + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("Command", renderer, {{"text", 0}, + {"weight-set", 2}, + {"weight", 3}}); + gtk_tree_view_append_column(view, column); + } + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("Key", renderer, {{"text", 1}, + {"weight-set", 2}, + {"weight", 3}}); + gtk_tree_view_append_column(view, column); + } + + view.show(); + scr.add(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; + ui::ListStore m_store; + public: + BuildCommandList(const char *filename, ui::ListStore store) : m_commandList(filename), + m_store(store) + { + } + + void visit(const char *name, Accelerator &accelerator) + { + StringOutputStream modifiers; + modifiers << accelerator; + + m_store.append(0, name, 1, modifiers.c_str(), 2, false, 3, 800); + + if (!m_commandList.failed()) { + int l = strlen(name); + m_commandList << name; + while (l++ < 25) { + m_commandList << ' '; + } + m_commandList << modifiers.c_str() << '\n'; + } + } + } visitor(path.c_str(), store); + + GlobalShortcuts_foreach(visitor); + } + + store.unref(); + } + } + + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + auto editbutton = create_dialog_button("Edit", (GCallback) accelerator_edit_button_clicked, &dialog); + vbox.pack_start(editbutton, FALSE, FALSE, 0); + + auto clearbutton = create_dialog_button("Clear", (GCallback) accelerator_clear_button_clicked, &dialog); + vbox.pack_start(clearbutton, FALSE, FALSE, 0); + + ui::Widget spacer = ui::Image(ui::New); + spacer.show(); + vbox.pack_start(spacer, TRUE, TRUE, 0); + + auto button = create_modal_dialog_button("Close", dialog.m_close_button); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_grab_default(button); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, (GtkAccelFlags) 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, (GtkAccelFlags) 0); + } + + modal_dialog_show(window, dialog); + window.destroy(); +} + +#include "profile/profile.h" + +const char *const COMMANDS_VERSION = "1.0-gtk-accelnames"; + +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 = gtk_accelerator_name(accelerator.key, accelerator.modifiers); + m_file << key; + 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; + } + + gtk_accelerator_parse(value, &accelerator.key, &accelerator.modifiers); + accelerator = accelerator; // fix modifiers + + if (accelerator.key != 0) { + ++m_count; + } else { + globalOutputStream() << "WARNING: failed to parse user command " << makeQuoted(name) << ": unknown key " + << makeQuoted(value) << "\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 0000000..5f264d2 --- /dev/null +++ b/radiant/commands.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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, int type); // 1 = command, 2 = toggle +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 Callback &)> &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 0000000..cdfed9c --- /dev/null +++ b/radiant/console.cpp @@ -0,0 +1,254 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "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 + if (!SettingsPath_get()[0]) { + return; // cannot open a log file yet + } + // 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() << "vedit.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 WorldSpawn '" WorldSpawn_VERSION "' compiled " __DATE__ "\n" WorldSpawn_ABOUTMSG "\n"; + } else { + ui::alert(ui::root, "Failed to create log file, check write permissions in WorldSpawn directory.\n", + "Console logging", ui::alert_type::OK, ui::alert_icon::Error); + } + } 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; + } +} + +ui::TextView g_console{ui::null}; + +void console_clear() +{ + g_console.text(""); +} + +void console_populate_popup(ui::TextView textview, ui::Menu menu, gpointer user_data) +{ + menu_separator(menu); + + ui::Widget item(ui::MenuItem("Clear")); + item.connect("activate", G_CALLBACK(console_clear), 0); + item.show(); + menu.add(item); +} + +gboolean destroy_set_null(ui::Window widget, ui::Widget *p) +{ + *p = ui::Widget{ui::null}; + return FALSE; +} + +WidgetFocusPrinter g_consoleWidgetFocusPrinter("console"); + +ui::Widget Console_constructWindow(ui::Window toplevel) +{ + auto scr = ui::ScrolledWindow(ui::New); + scr.overflow(ui::Policy::AUTOMATIC, ui::Policy::AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + scr.show(); + + { + auto text = ui::TextView(ui::New); + text.dimensions(0, -1); // allow shrinking + gtk_text_view_set_wrap_mode(text, GTK_WRAP_WORD); + gtk_text_view_set_editable(text, FALSE); + scr.add(text); + text.show(); + g_console = text; + + //globalExtendedASCIICharacterSet().print(); + + widget_connect_escape_clear_focus_widget(g_console); + + //g_consoleWidgetFocusPrinter.connect(g_console); + + g_console.connect("populate-popup", G_CALLBACK(console_populate_popup), 0); + g_console.connect("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, NULL); + 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) { + auto buffer = gtk_text_view_get_buffer(g_console); + + GtkTextIter iter; + gtk_text_buffer_get_end_iter(buffer, &iter); + + static auto end = gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE); + + const GdkColor yellow = {0, 0xb0ff, 0xb0ff, 0x0000}; + const GdkColor red = {0, 0xffff, 0x0000, 0x0000}; + + static auto error_tag = gtk_text_buffer_create_tag(buffer, "red_foreground", "foreground-gdk", &red, NULL); + static auto warning_tag = gtk_text_buffer_create_tag(buffer, "yellow_foreground", "foreground-gdk", &yellow, + NULL); + static auto standard_tag = gtk_text_buffer_create_tag(buffer, "black_foreground", NULL); + 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 << 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(g_console, end); + + if (!ScreenUpdates_Enabled() && gtk_widget_get_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 0000000..551b696 --- /dev/null +++ b/radiant/console.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_CONSOLE_H ) +#define INCLUDED_CONSOLE_H + +#include +#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(); + +ui::Widget Console_constructWindow(ui::Window 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 0000000..fab905a --- /dev/null +++ b/radiant/csg.cpp @@ -0,0 +1,653 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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(); + } + } +} + +void Face_makeRoom(Face &face, const Brush &brush, brush_vector_t &out, float offset) +{ + if (face.contributes()) { + face.getPlane().offset(offset); + out.push_back(new Brush(brush)); + face.getPlane().offset(-offset); + Face *newFace = out.back()->addFace(face); + if (newFace != 0) { + newFace->flipWinding(); + newFace->planeChanged(); + } + } +} + +void Brush_makeHollow(const Brush &brush, brush_vector_t &out, float offset) +{ + Brush_forEachFace(brush, [&](Face &face) { + Face_makeBrush(face, brush, out, offset); + }); +} + +void Brush_makeRoom(const Brush &brush, brush_vector_t &out, float offset) +{ + Brush_forEachFace(brush, [&](Face &face) { + Face_makeRoom(face, brush, out, offset); + }); +} + +class BrushHollowSelectedWalker : public scene::Graph::Walker { + float m_offset; + bool m_makeRoom; +public: + BrushHollowSelectedWalker(float offset, bool makeRoom) + : m_offset(offset), m_makeRoom(makeRoom) + { + } + + 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; + + if (m_makeRoom) { + Brush_makeRoom(*brush, out, m_offset); + } + else { + 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, bool makeRoom) +{ + GlobalSceneGraph().traverse(BrushHollowSelectedWalker(GetGridSize(), makeRoom)); + GlobalSceneGraph().traverse(BrushDeleteSelected()); +} + +/* + ============= + CSG_MakeHollow + ============= + */ + +void CSG_MakeHollow(void) +{ + UndoableCommand undo("brushHollow"); + + Scene_BrushMakeHollow_Selected(GlobalSceneGraph(), false); + + SceneChangeNotify(); +} + +void CSG_MakeRoom(void) +{ + UndoableCommand undo("brushRoom"); + + Scene_BrushMakeHollow_Selected(GlobalSceneGraph(), true); + + SceneChangeNotify(); +} + +template +class RemoveReference { +public: + typedef Type type; +}; + +template +class RemoveReference { +public: + typedef Type type; +}; + +template +class Dereference { + const Functor &functor; +public: + Dereference(const Functor &functor) : functor(functor) + { + } + + get_result_type operator()(typename RemoveReference>::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 get_argument FirstBound; + FirstBound firstBound; +public: + BindArguments1(FirstBound firstBound) + : firstBound(firstBound) + { + } + + get_result_type operator()(get_argument firstArgument) const + { + return Caller::call(firstArgument, firstBound); + } +}; + +template +class BindArguments2 { + typedef get_argument FirstBound; + typedef get_argument SecondBound; + FirstBound firstBound; + SecondBound secondBound; +public: + BindArguments2(FirstBound firstBound, SecondBound secondBound) + : firstBound(firstBound), secondBound(secondBound) + { + } + + get_result_type operator()(get_argument 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 Function 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; + (*i)->removeEmptyFaces(); + if (!(*i)->empty()) { + NodeSmartReference node((new BrushNode())->node()); + Node_getBrush(node)->copy(*(*i)); + delete (*i); + Node_getTraversable(path.parent())->insert(node); + } else { + delete (*i); + } + } + 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 0000000..886680e --- /dev/null +++ b/radiant/csg.h @@ -0,0 +1,54 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if !defined( INCLUDED_CSG_H ) +#define INCLUDED_CSG_H + +void CSG_MakeHollow(void); + +void CSG_MakeRoom(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 0000000..3b59f6a --- /dev/null +++ b/radiant/dialog.cpp @@ -0,0 +1,716 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +#include "debugging/debugging.h" + + +#include "mainframe.h" + +#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" + + +ui::Entry DialogEntry_new() +{ + auto entry = ui::Entry(ui::New); + entry.show(); + entry.dimensions(64, -1); + return entry; +} + +class DialogEntryRow { +public: + DialogEntryRow(ui::Widget row, ui::Entry entry) : m_row(row), m_entry(entry) + { + } + + ui::Widget m_row; + ui::Entry m_entry; +}; + +DialogEntryRow DialogEntryRow_new(const char *name) +{ + auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0); + alignment.show(); + + auto entry = DialogEntry_new(); + alignment.add(entry); + + return DialogEntryRow(ui::Widget(DialogRow_new(name, alignment)), entry); +} + + +ui::SpinButton 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; + } + auto spin = ui::SpinButton(ui::Adjustment(value, lower, upper, step, 10, 0), step, digits); + spin.show(); + spin.dimensions(64, -1); + return spin; +} + +class DialogSpinnerRow { +public: + DialogSpinnerRow(ui::Widget row, ui::SpinButton spin) : m_row(row), m_spin(spin) + { + } + + ui::Widget m_row; + ui::SpinButton m_spin; +}; + +DialogSpinnerRow DialogSpinnerRow_new(const char *name, double value, double lower, double upper, int fraction) +{ + auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0); + alignment.show(); + + auto spin = DialogSpinner_new(value, lower, upper, fraction); + alignment.add(spin); + + return DialogSpinnerRow(ui::Widget(DialogRow_new(name, alignment)), spin); +} + + +struct BoolToggle { + static void Export(const ui::ToggleButton &self, const Callback &returnz) + { + returnz(self.active()); + } + + static void Import(ui::ToggleButton &self, bool value) + { + self.active(value); + } +}; + +using BoolToggleImportExport = PropertyAdaptor; + +struct IntEntry { + static void Export(const ui::Entry &self, const Callback &returnz) + { + returnz(atoi(gtk_entry_get_text(self))); + } + + static void Import(ui::Entry &self, int value) + { + entry_set_int(self, value); + } +}; + +using IntEntryImportExport = PropertyAdaptor; + +struct IntRadio { + static void Export(const ui::RadioButton &self, const Callback &returnz) + { + returnz(radio_button_get_active(self)); + } + + static void Import(ui::RadioButton &self, int value) + { + radio_button_set_active(self, value); + } +}; + +using IntRadioImportExport = PropertyAdaptor; + +struct IntCombo { + static void Export(const ui::ComboBox &self, const Callback &returnz) + { + returnz(gtk_combo_box_get_active(self)); + } + + static void Import(ui::ComboBox &self, int value) + { + gtk_combo_box_set_active(self, value); + } +}; + +using IntComboImportExport = PropertyAdaptor; + +struct IntAdjustment { + static void Export(const ui::Adjustment &self, const Callback &returnz) + { + returnz(int(gtk_adjustment_get_value(self))); + } + + static void Import(ui::Adjustment &self, int value) + { + gtk_adjustment_set_value(self, value); + } +}; + +using IntAdjustmentImportExport = PropertyAdaptor; + +struct IntSpinner { + static void Export(const ui::SpinButton &self, const Callback &returnz) + { + returnz(gtk_spin_button_get_value_as_int(self)); + } + + static void Import(ui::SpinButton &self, int value) + { + gtk_spin_button_set_value(self, value); + } +}; + +using IntSpinnerImportExport = PropertyAdaptor; + +struct TextEntry { + static void Export(const ui::Entry &self, const Callback &returnz) + { + returnz(gtk_entry_get_text(self)); + } + + static void Import(ui::Entry &self, const char *value) + { + self.text(value); + } +}; + +using TextEntryImportExport = PropertyAdaptor; + +struct SizeEntry { + static void Export(const ui::Entry &self, const Callback &returnz) + { + int value = atoi(gtk_entry_get_text(self)); + if (value < 0) { + value = 0; + } + returnz(value); + } + + static void Import(ui::Entry &self, std::size_t value) + { + entry_set_int(self, int(value)); + } +}; + +using SizeEntryImportExport = PropertyAdaptor; + +struct FloatEntry { + static void Export(const ui::Entry &self, const Callback &returnz) + { + returnz(float(atof(gtk_entry_get_text(self)))); + } + + static void Import(ui::Entry &self, float value) + { + entry_set_float(self, value); + } +}; + +using FloatEntryImportExport = PropertyAdaptor; + +struct FloatSpinner { + static void Export(const ui::SpinButton &self, const Callback &returnz) + { + returnz(float(gtk_spin_button_get_value(self))); + } + + static void Import(ui::SpinButton &self, float value) + { + gtk_spin_button_set_value(self, value); + } +}; + +using FloatSpinnerImportExport = PropertyAdaptor; + + +template +class CallbackDialogData : public DLG_DATA { + Property m_pWidget; + Property m_pData; + +public: + CallbackDialogData(const Property &pWidget, const Property &pData) + : m_pWidget(pWidget), m_pData(pData) + { + } + + void release() + { + delete this; + } + + void importData() const + { + m_pData.get(m_pWidget.set); + } + + void exportData() const + { + m_pWidget.get(m_pData.set); + } +}; + +template +void AddDataCustom(DialogDataList &self, typename Widget::Type widget, Property const &property) +{ + using Self = typename Widget::Type; + using T = typename Widget::Other; + using native = typename std::remove_pointer::type; + struct Wrapper { + static void Export(const native &self, const Callback &returnz) + { + native *p = &const_cast(self); + auto widget = Self::from(p); + Widget::Get::thunk_(widget, returnz); + } + + static void Import(native &self, T value) + { + native *p = &self; + auto widget = Self::from(p); + Widget::Set::thunk_(widget, value); + } + }; + self.push_back(new CallbackDialogData( + make_property>(*static_cast(widget)), + property + )); +} + +template +void AddData(DialogDataList &self, typename Widget::Type widget, D &data) +{ + AddDataCustom(self, widget, make_property>(data)); +} + +// ============================================================================= +// Dialog class + +Dialog::Dialog() : m_window(ui::null), m_parent(ui::null) +{ +} + +Dialog::~Dialog() +{ + for (DialogDataList::iterator i = m_data.begin(); i != m_data.end(); ++i) { + (*i)->release(); + } + + ASSERT_MESSAGE(!m_window, "dialog window not destroyed"); +} + +void Dialog::ShowDlg() +{ + ASSERT_MESSAGE(m_window, "dialog was not constructed"); + importData(); + m_window.show(); +} + +void Dialog::HideDlg() +{ + ASSERT_MESSAGE(m_window, "dialog was not constructed"); + exportData(); + m_window.hide(); +} + +static gint delete_event_callback(ui::Widget widget, GdkEvent *event, gpointer data) +{ + reinterpret_cast

( data )->HideDlg(); + reinterpret_cast( data )->EndModal(eIDCANCEL); + return TRUE; +} + +void Dialog::Create() +{ + ASSERT_MESSAGE(!m_window, "dialog cannot be constructed"); + + m_window = BuildDialog(); + m_window.connect("delete_event", G_CALLBACK(delete_event_callback), this); +} + +void Dialog::Destroy() +{ + ASSERT_MESSAGE(m_window, "dialog cannot be destroyed"); + + m_window.destroy(); + m_window = ui::Window{ui::null}; +} + + +void Dialog::AddBoolToggleData(ui::ToggleButton widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddIntRadioData(ui::RadioButton widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddTextEntryData(ui::Entry widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddIntEntryData(ui::Entry widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddSizeEntryData(ui::Entry widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddFloatEntryData(ui::Entry widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddFloatSpinnerData(ui::SpinButton widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddIntSpinnerData(ui::SpinButton widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddIntAdjustmentData(ui::Adjustment widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + +void Dialog::AddIntComboData(ui::ComboBox widget, Property const &cb) +{ + AddDataCustom(m_data, widget, cb); +} + + +void Dialog::AddDialogData(ui::ToggleButton widget, bool &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::RadioButton widget, int &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::Entry widget, CopiedString &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::Entry widget, int &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::Entry widget, std::size_t &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::Entry widget, float &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::SpinButton widget, float &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::SpinButton widget, int &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::Adjustment widget, int &data) +{ + AddData(m_data, widget, data); +} + +void Dialog::AddDialogData(ui::ComboBox widget, int &data) +{ + AddData(m_data, 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_TRUE((bool) m_window); + if (ret == eIDOK) { + exportData(); + } + + m_window.hide(); + + PostModal(m_modal.ret); + + return m_modal.ret; +} + + +ui::CheckButton Dialog::addCheckBox(ui::VBox vbox, const char *name, const char *flag, Property const &cb) +{ + auto check = ui::CheckButton(flag); + check.show(); + AddBoolToggleData(check, cb); + + DialogVBox_packRow(vbox, ui::Widget(DialogRow_new(name, check))); + return check; +} + +ui::CheckButton Dialog::addCheckBox(ui::VBox vbox, const char *name, const char *flag, bool &data) +{ + return addCheckBox(vbox, name, flag, make_property(data)); +} + +void Dialog::addCombo(ui::VBox vbox, const char *name, StringArrayRange values, Property const &cb) +{ + auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0); + alignment.show(); + { + auto combo = ui::ComboBoxText(ui::New); + + for (StringArrayRange::Iterator i = values.first; i != values.last; ++i) { + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), *i); + } + + AddIntComboData(combo, cb); + + combo.show(); + alignment.add(combo); + } + + auto row = DialogRow_new(name, alignment); + DialogVBox_packRow(vbox, row); +} + +void Dialog::addCombo(ui::VBox vbox, const char *name, int &data, StringArrayRange values) +{ + addCombo(vbox, name, values, make_property(data)); +} + +void +Dialog::addSlider(ui::VBox 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) +{ +#if 0 + if ( draw_value == FALSE ) { + auto hbox2 = ui::HBox( FALSE, 0 ); + hbox2.show(); + vbox.pack_start( hbox2 , FALSE, FALSE, 0 ); + { + ui::Widget label = ui::Label( low ); + label.show(); + hbox2.pack_start( label, FALSE, FALSE, 0 ); + } + { + ui::Widget label = ui::Label( high ); + label.show(); + hbox2.pack_end(label, FALSE, FALSE, 0); + } + } +#endif + + // adjustment + auto adj = ui::Adjustment(value, lower, upper, step_increment, page_increment, 0); + AddIntAdjustmentData(adj, make_property(data)); + + // scale + auto alignment = ui::Alignment(0.0, 0.5, 1.0, 0.0); + alignment.show(); + + ui::Widget scale = ui::HScale(adj); + gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_LEFT); + scale.show(); + alignment.add(scale); + + gtk_scale_set_draw_value(GTK_SCALE(scale), draw_value); + gtk_scale_set_digits(GTK_SCALE(scale), 0); + + auto row = DialogRow_new(name, alignment); + DialogVBox_packRow(vbox, row); +} + +void Dialog::addRadio(ui::VBox vbox, const char *name, StringArrayRange names, Property const &cb) +{ + auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0); + alignment.show();; + { + RadioHBox radioBox = RadioHBox_new(names); + alignment.add(radioBox.m_hbox); + AddIntRadioData(radioBox.m_radio, cb); + } + + auto row = DialogRow_new(name, alignment); + DialogVBox_packRow(vbox, row); +} + +void Dialog::addRadio(ui::VBox vbox, const char *name, int &data, StringArrayRange names) +{ + addRadio(vbox, name, names, make_property(data)); +} + +void Dialog::addRadioIcons(ui::VBox vbox, const char *name, StringArrayRange icons, Property const &cb) +{ + auto table = ui::Table(2, icons.last - icons.first, FALSE); + table.show(); + + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + + GSList *group = 0; + ui::RadioButton radio{ui::null}; + for (StringArrayRange::Iterator icon = icons.first; icon != icons.last; ++icon) { + guint pos = static_cast( icon - icons.first ); + auto image = new_local_image(*icon); + image.show(); + table.attach(image, {pos, pos + 1, 0, 1}, {0, 0}); + + radio = ui::RadioButton::from(gtk_radio_button_new(group)); + radio.show(); + table.attach(radio, {pos, pos + 1, 1, 2}, {0, 0}); + + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)); + } + + AddIntRadioData(radio, cb); + + DialogVBox_packRow(vbox, DialogRow_new(name, table)); +} + +void Dialog::addRadioIcons(ui::VBox vbox, const char *name, int &data, StringArrayRange icons) +{ + addRadioIcons(vbox, name, icons, make_property(data)); +} + +ui::Widget Dialog::addIntEntry(ui::VBox vbox, const char *name, Property const &cb) +{ + DialogEntryRow row(DialogEntryRow_new(name)); + AddIntEntryData(row.m_entry, cb); + DialogVBox_packRow(vbox, row.m_row); + return row.m_row; +} + +ui::Widget Dialog::addSizeEntry(ui::VBox vbox, const char *name, Property const &cb) +{ + DialogEntryRow row(DialogEntryRow_new(name)); + AddSizeEntryData(row.m_entry, cb); + DialogVBox_packRow(vbox, row.m_row); + return row.m_row; +} + +ui::Widget Dialog::addFloatEntry(ui::VBox vbox, const char *name, Property const &cb) +{ + DialogEntryRow row(DialogEntryRow_new(name)); + AddFloatEntryData(row.m_entry, cb); + DialogVBox_packRow(vbox, row.m_row); + return row.m_row; +} + +ui::Widget +Dialog::addPathEntry(ui::VBox vbox, const char *name, bool browse_directory, Property const &cb) +{ + PathEntry pathEntry = PathEntry_new(); + pathEntry.m_button.connect("clicked", G_CALLBACK( + browse_directory ? button_clicked_entry_browse_directory : button_clicked_entry_browse_file), + pathEntry.m_entry); + + AddTextEntryData(pathEntry.m_entry, cb); + + auto row = DialogRow_new(name, ui::Widget(pathEntry.m_frame)); + DialogVBox_packRow(vbox, row); + + return row; +} + +ui::Widget Dialog::addPathEntry(ui::VBox vbox, const char *name, CopiedString &data, bool browse_directory) +{ + return addPathEntry(vbox, name, browse_directory, make_property(data)); +} + +ui::SpinButton +Dialog::addSpinner(ui::VBox vbox, const char *name, double value, double lower, double upper, Property const &cb) +{ + DialogSpinnerRow row(DialogSpinnerRow_new(name, value, lower, upper, 1)); + AddIntSpinnerData(row.m_spin, cb); + DialogVBox_packRow(vbox, row.m_row); + return row.m_spin; +} + +ui::SpinButton Dialog::addSpinner(ui::VBox vbox, const char *name, int &data, double value, double lower, double upper) +{ + return addSpinner(vbox, name, value, lower, upper, make_property(data)); +} + +ui::SpinButton +Dialog::addSpinner(ui::VBox vbox, const char *name, double value, double lower, double upper, Property const &cb) +{ + DialogSpinnerRow row(DialogSpinnerRow_new(name, value, lower, upper, 10)); + AddFloatSpinnerData(row.m_spin, cb); + DialogVBox_packRow(vbox, row.m_row); + return row.m_spin; +} diff --git a/radiant/dialog.h b/radiant/dialog.h new file mode 100644 index 0000000..d4b26d5 --- /dev/null +++ b/radiant/dialog.h @@ -0,0 +1,194 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "property.h" + +#include "generic/callback.h" +#include "gtkutil/dialog.h" +#include "generic/callback.h" +#include "string/string.h" + +struct DLG_DATA { + virtual ~DLG_DATA() = default; + + virtual void release() = 0; + + virtual void importData() const = 0; + + virtual void exportData() const = 0; +}; + + +template +class CallbackDialogData; + +typedef std::list DialogDataList; + +class Dialog { + ui::Window m_window; + DialogDataList m_data; +public: + ModalDialog m_modal; + ui::Window 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 ui::Window 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(); + + ui::Window GetWidget() + { + return m_window; + } + + const ui::Window GetWidget() const + { + return m_window; + } + + ui::CheckButton addCheckBox(ui::VBox vbox, const char *name, const char *flag, Property const &cb); + + ui::CheckButton addCheckBox(ui::VBox vbox, const char *name, const char *flag, bool &data); + + void addCombo(ui::VBox vbox, const char *name, StringArrayRange values, Property const &cb); + + void addCombo(ui::VBox vbox, const char *name, int &data, StringArrayRange values); + + void addSlider(ui::VBox 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); + + void addRadio(ui::VBox vbox, const char *name, StringArrayRange names, Property const &cb); + + void addRadio(ui::VBox vbox, const char *name, int &data, StringArrayRange names); + + void addRadioIcons(ui::VBox vbox, const char *name, StringArrayRange icons, Property const &cb); + + void addRadioIcons(ui::VBox vbox, const char *name, int &data, StringArrayRange icons); + + ui::Widget addIntEntry(ui::VBox vbox, const char *name, Property const &cb); + + ui::Widget addEntry(ui::VBox vbox, const char *name, int &data) + { + return addIntEntry(vbox, name, make_property(data)); + } + + ui::Widget addSizeEntry(ui::VBox vbox, const char *name, Property const &cb); + + ui::Widget addEntry(ui::VBox vbox, const char *name, std::size_t &data) + { + return addSizeEntry(vbox, name, make_property(data)); + } + + ui::Widget addFloatEntry(ui::VBox vbox, const char *name, Property const &cb); + + ui::Widget addEntry(ui::VBox vbox, const char *name, float &data) + { + return addFloatEntry(vbox, name, make_property(data)); + } + + ui::Widget addPathEntry(ui::VBox vbox, const char *name, bool browse_directory, Property const &cb); + + ui::Widget addPathEntry(ui::VBox vbox, const char *name, CopiedString &data, bool directory); + + ui::SpinButton addSpinner(ui::VBox vbox, const char *name, int &data, double value, double lower, double upper); + + ui::SpinButton + addSpinner(ui::VBox vbox, const char *name, double value, double lower, double upper, Property const &cb); + + ui::SpinButton + addSpinner(ui::VBox vbox, const char *name, double value, double lower, double upper, Property const &cb); + +protected: + + void AddBoolToggleData(ui::ToggleButton object, Property const &cb); + + void AddIntRadioData(ui::RadioButton object, Property const &cb); + + void AddTextEntryData(ui::Entry object, Property const &cb); + + void AddIntEntryData(ui::Entry object, Property const &cb); + + void AddSizeEntryData(ui::Entry object, Property const &cb); + + void AddFloatEntryData(ui::Entry object, Property const &cb); + + void AddFloatSpinnerData(ui::SpinButton object, Property const &cb); + + void AddIntSpinnerData(ui::SpinButton object, Property const &cb); + + void AddIntAdjustmentData(ui::Adjustment object, Property const &cb); + + void AddIntComboData(ui::ComboBox object, Property const &cb); + + void AddDialogData(ui::ToggleButton object, bool &data); + + void AddDialogData(ui::RadioButton object, int &data); + + void AddDialogData(ui::Entry object, CopiedString &data); + + void AddDialogData(ui::Entry object, int &data); + + void AddDialogData(ui::Entry object, std::size_t &data); + + void AddDialogData(ui::Entry object, float &data); + + void AddDialogData(ui::SpinButton object, float &data); + + void AddDialogData(ui::SpinButton object, int &data); + + void AddDialogData(ui::Adjustment object, int &data); + + void AddDialogData(ui::ComboBox object, int &data); +}; + +#endif diff --git a/radiant/eclass.cpp b/radiant/eclass.cpp new file mode 100644 index 0000000..d1722c3 --- /dev/null +++ b/radiant/eclass.cpp @@ -0,0 +1,399 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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); + } +} + +void Eclass_forEachPoint(EntityClassVisitor &visitor) +{ + for (EntityClasses::iterator i = g_entityClasses.begin(); i != g_entityClasses.end(); ++i) { + if ((*i).second->fixedsize == true) { + 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; + +void EntityClassQuake3_constructDirectory(const char *directory, const char *extension, Paths &paths) +{ + globalOutputStream() << "EntityClass: searching " << makeQuoted(directory) << " for *." << extension << '\n'; + Directory_forEach(directory, matchFileExtension(extension, [&](const char *name) { + paths.insert(Paths::value_type(name, 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.forEachPoint = &Eclass_forEachPoint; + m_eclassmanager.attach = &EntityClass_attach; + m_eclassmanager.detach = &EntityClass_detach; + m_eclassmanager.realise = &EntityClass_realise; + m_eclassmanager.unrealise = &EntityClass_unrealise; + + Radiant_attachGameToolsPathObserver(g_EntityClassQuake3); + Radiant_attachGameModeObserver(g_EntityClassQuake3); + Radiant_attachGameNameObserver(g_EntityClassQuake3); + } + + ~EclassManagerAPI() + { + Radiant_detachGameNameObserver(g_EntityClassQuake3); + Radiant_detachGameModeObserver(g_EntityClassQuake3); + Radiant_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 0000000..d4bfd1f --- /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 0000000..daa0e70 --- /dev/null +++ b/radiant/eclass_def.cpp @@ -0,0 +1,378 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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; i < MAX_FLAGS; i++) { + p = COM_Parse(p); + if (!p) { + break; + } + strcpy(e->flagnames[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 0000000..102fe4f --- /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 0000000..247c160 --- /dev/null +++ b/radiant/eclass_doom3.cpp @@ -0,0 +1,827 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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) do { if (!( expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } } while (0) + +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); + if (string_equal_n(key, "editor_", 7)) { + globalErrorStream() << "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", makeCallbackF(EntityClassDoom3_loadFile)); + + { + 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 0000000..9dcd8bb --- /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 0000000..c15af65 --- /dev/null +++ b/radiant/eclass_fgd.cpp @@ -0,0 +1,697 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "mainframe.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); +} + +const char *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() << "/entities.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; + + Radiant_attachGameToolsPathObserver(g_EntityClassFGD); + Radiant_attachGameNameObserver(g_EntityClassFGD); + } + + ~EntityClassFGDAPI() + { + Radiant_detachGameNameObserver(g_EntityClassFGD); + Radiant_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 0000000..9eafc54 --- /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 0000000..5d9d317 --- /dev/null +++ b/radiant/eclass_xml.cpp @@ -0,0 +1,598 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 > 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 0000000..9b6f9dd --- /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 0000000..13a23b7 --- /dev/null +++ b/radiant/entity.cpp @@ -0,0 +1,637 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" + +#include "uilib/uilib.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)); +} + +/* to worldspawn */ +void Entity_ungroupSelected() +{ + if (GlobalSelectionSystem().countSelected() < 1) { + return; + } + + UndoableCommand undo("ungroupSelectedEntities"); + + /* to auto-select */ + /*scene::Path entitypath(makeReference(GlobalSceneGraph().root())); + scene::Instance& einstance = findInstance(entitypath);*/ + + while (GlobalSelectionSystem().countSelected()) { + 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(); + + /* remove it from the path if its not an ent? */ + if (!Node_isEntity(path.top())) { + path.pop(); + } + + /* we'll cause a crash otherwise */ + Entity *entity = Node_getEntity(path.top()); + if (entity != 0 && entity->getEntityClass().fixedsize) { + /* Unselect us */ + Instance_setSelected(instance, false); + continue; + } + + /* this will sadly unselect anything it touches (merge to world) */ + if (entity != 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); + } + } + } + + /*Scene_forEachChildSelectable(SelectableSetSelected(true), einstance.path());*/ +} + + +class EntityFindSelected : public scene::Graph::Walker { +public: + mutable const scene::Path *groupPath; + mutable scene::Instance *groupInstance; + + EntityFindSelected() : groupPath(0), groupInstance(0) + { + } + + 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_getSelectable(instance)->isSelected() + && node_is_group(path.top()) + && !groupPath) { + groupPath = &path; + groupInstance = &instance; + } + } +}; + +class EntityGroupSelected : public scene::Graph::Walker { + NodeSmartReference group, worldspawn; +//typedef std::pair DeletionPair; +//Stack deleteme; +public: + EntityGroupSelected(const scene::Path &p) : group(p.top().get()), worldspawn(Map_FindOrInsertWorldspawn(g_map)) + { + } + + bool pre(const scene::Path &path, scene::Instance &instance) const + { + return true; + } + + void post(const scene::Path &path, scene::Instance &instance) const + { + Selectable *selectable = Instance_getSelectable(instance); + + if (selectable && selectable->isSelected()) { + Entity *entity = Node_getEntity(path.top()); + if (entity == 0 && Node_isPrimitive(path.top())) { + NodeSmartReference child(path.top().get()); + NodeSmartReference parent(path.parent().get()); + + if (path.size() >= 3 && parent != worldspawn) { + NodeSmartReference parentparent(path[path.size() - 3].get()); + + Node_getTraversable(parent)->erase(child); + Node_getTraversable(group)->insert(child); + + if (Node_getTraversable(parent)->empty()) { + //deleteme.push(DeletionPair(parentparent, parent)); + Node_getTraversable(parentparent)->erase(parent); + } + } else { + Node_getTraversable(parent)->erase(child); + Node_getTraversable(group)->insert(child); + } + } + } + } +}; + +void Scene_DeleteEmpty(); +void Entity_groupSelected() +{ + if (GlobalSelectionSystem().countSelected() < 1) { + return; + } + + UndoableCommand undo("groupSelectedEntities"); + const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path(); + + Entity *entity = Node_getEntity(path.top()); + + if (entity != 0) { + const char *strClassname = entity->getKeyValue("classname"); + if (!string_empty(strClassname)) { + Entity_createFromSelection(strClassname, g_vector3_identity); + } + } +} + +void Entity_groupMake() +{ + if (GlobalSelectionSystem().countSelected() < 1) { + return; + } + + UndoableCommand undo("entityCreate -class func_group" ); + Entity_createFromSelection("func_group", g_vector3_identity); + /*Scene_DeleteEmpty();*/ +} + + +void Entity_connectSelected() +{ + if (GlobalSelectionSystem().countSelected() == 2) { + GlobalEntityCreator().connectEntities( + GlobalSelectionSystem().penultimateSelected().path(), + GlobalSelectionSystem().ultimateSelected().path(), + 0 + ); + } else { + globalErrorStream() << "entityConnectSelected: exactly two instances must be selected\n"; + } +} + +void Entity_killconnectSelected() +{ + if (GlobalSelectionSystem().countSelected() == 2) { + GlobalEntityCreator().connectEntities( + GlobalSelectionSystem().penultimateSelected().path(), + GlobalSelectionSystem().ultimateSelected().path(), + 1 + ); + } else { + globalErrorStream() << "entityKillConnectSelected: 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) +{ + EntityClass *entityClass = GlobalEntityClassManager().findOrInsert(name, true); + + bool isModel = string_equal_nocase(name, "prop_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)); + + /* this is where ghost entities happen */ + 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) + { + 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 { + Scene_parentSelectedBrushesToEntity(GlobalSceneGraph(), node); + Scene_forEachChildSelectable(SelectableSetSelected(true), instance.path()); + Scene_DeleteEmpty(); + } + + // 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(MainFrame_getWindow()); + if (model != 0) { + Node_getEntity(node)->setKeyValue("model", model); + } + } +} + +#if 0 +bool DoNormalisedColor( Vector3& color ){ + if ( !color_dialog( 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; +} +#endif + +void NormalizeColor(Vector3 &color) +{ + // 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; + } +} + +void Entity_normalizeColor() +{ + 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; + NormalizeColor(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); + } + } + } + } +} + +void Entity_setColour() +{ + if (GlobalSelectionSystem().countSelected() != 0) { + bool normalize = false; + 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 (color_dialog(MainFrame_getWindow(), g_entity_globals.color_entity)) { + if (normalize) { + NormalizeColor(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(ui::Widget 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 = parent.file_dialog(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; +} + +struct LightRadii { + static void Export(const EntityCreator &self, const Callback &returnz) + { + returnz(self.getLightRadii()); + } + + static void Import(EntityCreator &self, bool value) + { + self.setLightRadii(value); + } +}; + +void Entity_constructPreferences(PreferencesPage &page) +{ + page.appendCheckBox( + "Show", "Light Radii", + make_property(GlobalEntityCreator()) + ); +} + +void Entity_constructPage(PreferenceGroup &group) +{ + PreferencesPage page(group.createPage("Entities", "Entity Display Preferences")); + Entity_constructPreferences(page); +} + +void Entity_registerPreferencesPage() +{ + PreferencesDialog_addDisplayPage(makeCallbackF(Entity_constructPage)); +} + + +void Entity_constructMenu(ui::Menu menu) +{ + create_menu_item_with_mnemonic(menu, "_To Worldspawn", "UngroupSelection"); + create_menu_item_with_mnemonic(menu, "_Regroup", "GroupSelection"); + create_menu_item_with_mnemonic(menu, "_Connect", "ConnectSelection"); + create_menu_item_with_mnemonic(menu, "_KillConnect", "KillConnectSelection"); + create_menu_item_with_mnemonic(menu, "_Select Color...", "EntityColor"); + create_menu_item_with_mnemonic(menu, "_Normalize Color...", "NormalizeColor"); +} + + +#include "preferencesystem.h" +#include "stringio.h" + +void Entity_Construct() +{ + GlobalCommands_insert("EntityColor", makeCallbackF(Entity_setColour), Accelerator('K')); + GlobalCommands_insert("NormalizeColor", makeCallbackF(Entity_normalizeColor)); + GlobalCommands_insert("ConnectSelection", makeCallbackF(Entity_connectSelected), + Accelerator('K', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("KillConnectSelection", makeCallbackF(Entity_killconnectSelected), + Accelerator('K', (GdkModifierType) (GDK_SHIFT_MASK))); + GlobalCommands_insert("GroupSelection", makeCallbackF(Entity_groupSelected)); + GlobalCommands_insert("UngroupSelection", makeCallbackF(Entity_ungroupSelected)); + GlobalCommands_insert("CreateFuncGroup", makeCallbackF(Entity_groupMake)); + + GlobalPreferenceSystem().registerPreference("SI_Colors5", make_property_string(g_entity_globals.color_entity)); + GlobalPreferenceSystem().registerPreference("LastLightIntensity", make_property_string(g_iLastLightIntensity)); + + Entity_registerPreferencesPage(); +} + +void Entity_Destroy() +{ +} diff --git a/radiant/entity.h b/radiant/entity.h new file mode 100644 index 0000000..a2a9f75 --- /dev/null +++ b/radiant/entity.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_ENTITY_H ) +#define INCLUDED_ENTITY_H + +#include + +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); + + +const char *misc_model_dialog(ui::Widget parent); + +void Entity_constructMenu(ui::Menu menu); + +void Entity_Construct(); + +void Entity_Destroy(); + +#endif diff --git a/radiant/entityinspector.cpp b/radiant/entityinspector.cpp new file mode 100644 index 0000000..6e78b3e --- /dev/null +++ b/radiant/entityinspector.cpp @@ -0,0 +1,1756 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public 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 + +#include "ientity.h" +#include "ifilesystem.h" +#include "imodel.h" +#include "iscenegraph.h" +#include "iselection.h" +#include "iundo.h" + +#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" +#include "groupdialog.h" + +ui::Entry numeric_entry_new() +{ + auto entry = ui::Entry(ui::New); + entry.show(); + entry.dimensions(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 ~EntityAttribute() = default; + + virtual ui::Widget getWidget() const = 0; + + virtual void update() = 0; + + virtual void release() = 0; +}; + +class BooleanAttribute : public EntityAttribute { + CopiedString m_key; + ui::CheckButton m_check; + + static gboolean toggled(ui::Widget widget, BooleanAttribute *self) + { + self->apply(); + return FALSE; + } + +public: + BooleanAttribute(const char *key) : + m_key(key), + m_check(ui::null) + { + auto check = ui::CheckButton(ui::New); + check.show(); + + m_check = check; + + guint handler = check.connect("toggled", G_CALLBACK(toggled), this); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler)); + + update(); + } + + ui::Widget getWidget() const + { + return m_check; + } + + void release() + { + delete this; + } + + void apply() + { + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), m_check.active() ? "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(m_check, atoi(value) != 0); + } else { + toggle_button_set_active_no_signal(m_check, false); + } + } + + typedef MemberCaller UpdateCaller; +}; + + +class StringAttribute : public EntityAttribute { + CopiedString m_key; + ui::Entry m_entry; + NonModalEntry m_nonModal; +public: + StringAttribute(const char *key) : + m_key(key), + m_entry(ui::null), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + auto entry = ui::Entry(ui::New); + entry.show(); + entry.dimensions(50, -1); + + m_entry = entry; + m_nonModal.connect(m_entry); + } + + ui::Widget getWidget() const + { + return m_entry; + } + + ui::Entry getEntry() const + { + return m_entry; + } + + void release() + { + delete this; + } + + void apply() + { + StringOutputStream value(64); + value << m_entry.text(); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str()); + } + + typedef MemberCaller ApplyCaller; + + void update() + { + StringOutputStream value(64); + value << SelectedEntity_getValueForKey(m_key.c_str()); + m_entry.text(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; + } + + ui::Widget getWidget() const + { + return m_entry.m_entry.m_frame; + } + + void apply() + { + StringOutputStream value(64); + value << m_entry.m_entry.m_entry.text(); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str()); + } + + typedef MemberCaller ApplyCaller; + + void update() + { + StringOutputStream value(64); + value << SelectedEntity_getValueForKey(m_key.c_str()); + m_entry.m_entry.m_entry.text(value.c_str()); + } + + typedef MemberCaller UpdateCaller; + + void browse(const BrowsedPathEntry::SetPathCallback &setPath) + { + const char *filename = misc_model_dialog(m_entry.m_entry.m_frame.window()); + + if (filename != 0) { + setPath(filename); + apply(); + } + } + + typedef MemberCaller BrowseCaller; +}; + +const char *browse_sound(ui::Widget 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 = parent.file_dialog(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; + } + + ui::Widget getWidget() const + { + return ui::Widget(m_entry.m_entry.m_frame); + } + + void apply() + { + StringOutputStream value(64); + value << m_entry.m_entry.m_entry.text(); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str()); + } + + typedef MemberCaller ApplyCaller; + + void update() + { + StringOutputStream value(64); + value << SelectedEntity_getValueForKey(m_key.c_str()); + m_entry.m_entry.m_entry.text(value.c_str()); + } + + typedef MemberCaller UpdateCaller; + + void browse(const BrowsedPathEntry::SetPathCallback &setPath) + { + const char *filename = browse_sound(m_entry.m_entry.m_frame.window()); + + if (filename != 0) { + setPath(filename); + apply(); + } + } + + typedef MemberCaller BrowseCaller; +}; + +inline double angle_normalised(double angle) +{ + return float_mod(angle, 360.0); +} + +class AngleAttribute : public EntityAttribute { + CopiedString m_key; + ui::Entry m_entry; + NonModalEntry m_nonModal; +public: + AngleAttribute(const char *key) : + m_key(key), + m_entry(ui::null), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + auto entry = numeric_entry_new(); + m_entry = entry; + m_nonModal.connect(m_entry); + } + + void release() + { + delete this; + } + + ui::Widget getWidget() const + { + return ui::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)); + m_entry.text(angle.c_str()); + } else { + m_entry.text("0"); + } + } + + typedef MemberCaller UpdateCaller; +}; + +namespace { + typedef const char *String; + const String buttons[] = {"up", "down", "z-axis"}; +} + +class DirectionAttribute : public EntityAttribute { + CopiedString m_key; + ui::Entry m_entry; + NonModalEntry m_nonModal; + RadioHBox m_radio; + NonModalRadio m_nonModalRadio; + ui::HBox m_hbox{ui::null}; +public: + DirectionAttribute(const char *key) : + m_key(key), + m_entry(ui::null), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)), + m_radio(RadioHBox_new(STRING_ARRAY_RANGE(buttons))), + m_nonModalRadio(ApplyRadioCaller(*this)) + { + auto entry = numeric_entry_new(); + m_entry = entry; + m_nonModal.connect(m_entry); + + m_nonModalRadio.connect(m_radio.m_radio); + + m_hbox = ui::HBox(FALSE, 4); + m_hbox.show(); + + m_hbox.pack_start(m_radio.m_hbox, TRUE, TRUE, 0); + m_hbox.pack_start(m_entry, TRUE, TRUE, 0); + } + + void release() + { + delete this; + } + + ui::Widget getWidget() const + { + return ui::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(m_entry, FALSE); + radio_button_set_active_no_signal(m_radio.m_radio, 0); + m_entry.text(""); + } else if (f == -2) { + gtk_widget_set_sensitive(m_entry, FALSE); + radio_button_set_active_no_signal(m_radio.m_radio, 1); + m_entry.text(""); + } else { + gtk_widget_set_sensitive(m_entry, TRUE); + radio_button_set_active_no_signal(m_radio.m_radio, 2); + StringOutputStream angle(32); + angle << angle_normalised(f); + m_entry.text(angle.c_str()); + } + } else { + m_entry.text("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: + ui::Entry m_roll; + ui::Entry m_pitch; + ui::Entry m_yaw; + + AnglesEntry() : m_roll(ui::null), m_pitch(ui::null), m_yaw(ui::null) + { + } +}; + +typedef BasicVector3 DoubleVector3; + +class AnglesAttribute : public EntityAttribute { + CopiedString m_key; + AnglesEntry m_angles; + NonModalEntry m_nonModal; + ui::HBox m_hbox; +public: + AnglesAttribute(const char *key) : + m_key(key), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)), + m_hbox(ui::HBox(TRUE, 4)) + { + m_hbox.show(); + { + auto entry = numeric_entry_new(); + m_hbox.pack_start(entry, TRUE, TRUE, 0); + m_angles.m_pitch = entry; + m_nonModal.connect(m_angles.m_pitch); + } + { + auto entry = numeric_entry_new(); + m_hbox.pack_start(entry, TRUE, TRUE, 0); + m_angles.m_yaw = entry; + m_nonModal.connect(m_angles.m_yaw); + } + { + auto entry = numeric_entry_new(); + m_hbox.pack_start(entry, TRUE, TRUE, 0); + m_angles.m_roll = entry; + m_nonModal.connect(m_angles.m_roll); + } + } + + void release() + { + delete this; + } + + ui::Widget getWidget() const + { + return ui::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()); + m_angles.m_pitch.text(angle.c_str()); + angle.clear(); + + angle << angle_normalised(pitch_yaw_roll.y()); + m_angles.m_yaw.text(angle.c_str()); + angle.clear(); + + angle << angle_normalised(pitch_yaw_roll.z()); + m_angles.m_roll.text(angle.c_str()); + angle.clear(); + } else { + m_angles.m_pitch.text("0"); + m_angles.m_yaw.text("0"); + m_angles.m_roll.text("0"); + } + } + + typedef MemberCaller UpdateCaller; +}; + +class Vector3Entry { +public: + ui::Entry m_x; + ui::Entry m_y; + ui::Entry m_z; + + Vector3Entry() : m_x(ui::null), m_y(ui::null), m_z(ui::null) + { + } +}; + +class Vector3Attribute : public EntityAttribute { + CopiedString m_key; + Vector3Entry m_vector3; + NonModalEntry m_nonModal; + ui::Box m_hbox{ui::null}; +public: + Vector3Attribute(const char *key) : + m_key(key), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + m_hbox = ui::HBox(TRUE, 4); + m_hbox.show(); + { + auto entry = numeric_entry_new(); + m_hbox.pack_start(entry, TRUE, TRUE, 0); + m_vector3.m_x = entry; + m_nonModal.connect(m_vector3.m_x); + } + { + auto entry = numeric_entry_new(); + m_hbox.pack_start(entry, TRUE, TRUE, 0); + m_vector3.m_y = entry; + m_nonModal.connect(m_vector3.m_y); + } + { + auto entry = numeric_entry_new(); + m_hbox.pack_start(entry, TRUE, TRUE, 0); + m_vector3.m_z = entry; + m_nonModal.connect(m_vector3.m_z); + } + } + + void release() + { + delete this; + } + + ui::Widget getWidget() const + { + return ui::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(); + m_vector3.m_x.text(buffer.c_str()); + buffer.clear(); + + buffer << x_y_z.y(); + m_vector3.m_y.text(buffer.c_str()); + buffer.clear(); + + buffer << x_y_z.z(); + m_vector3.m_z.text(buffer.c_str()); + buffer.clear(); + } else { + m_vector3.m_x.text("0"); + m_vector3.m_y.text("0"); + m_vector3.m_z.text("0"); + } + } + + typedef MemberCaller UpdateCaller; +}; + +class NonModalComboBox { + Callback m_changed; + guint m_changedHandler; + + static gboolean changed(ui::ComboBox widget, NonModalComboBox *self) + { + self->m_changed(); + return FALSE; + } + +public: + NonModalComboBox(const Callback &changed) : m_changed(changed), m_changedHandler(0) + { + } + + void connect(ui::ComboBox combo) + { + m_changedHandler = combo.connect("changed", G_CALLBACK(changed), this); + } + + void setActive(ui::ComboBox 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; + ui::ComboBox m_combo; + NonModalComboBox m_nonModal; + const ListAttributeType &m_type; +public: + ListAttribute(const char *key, const ListAttributeType &type) : + m_key(key), + m_combo(ui::null), + m_nonModal(ApplyCaller(*this)), + m_type(type) + { + auto combo = ui::ComboBoxText(ui::New); + + for (ListAttributeType::const_iterator i = type.begin(); i != type.end(); ++i) { + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), (*i).first.c_str()); + } + + combo.show(); + m_nonModal.connect(combo); + + m_combo = combo; + } + + void release() + { + delete this; + } + + ui::Widget getWidget() const + { + return ui::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 { + ui::Widget g_entity_split1{ui::null}; + ui::Widget g_entity_split2{ui::null}; + int g_entitysplit1_position; + int g_entitysplit2_position; + + bool g_entityInspector_windowConstructed = false; + + ui::TreeView g_entityClassList{ui::null}; + ui::TextView g_entityClassComment{ui::null}; + + GtkCheckButton *g_entitySpawnflagsCheck[MAX_FLAGS]; + + ui::Entry g_entityKeyEntry{ui::null}; + ui::Entry g_entityValueEntry{ui::null}; + + ui::ListStore g_entlist_store{ui::null}; + ui::ListStore g_entprops_store{ui::null}; + 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.. + ui::Table g_spawnflagsTable{ui::null}; + + ui::VBox g_attributeBox{ui::null}; + 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 { + ui::ListStore store; +public: + EntityClassListStoreAppend(ui::ListStore store_) : store(store_) + { + } + + void visit(EntityClass *e) + { + store.append(0, e->name(), 1, e); + } +}; + +void EntityClassList_fill() +{ + EntityClassListStoreAppend append(g_entlist_store); + GlobalEntityClassManager().forEach(append); +} + +void EntityClassList_clear() +{ + g_entlist_store.clear(); +} + +void SetComment(EntityClass *eclass) +{ + if (eclass == g_current_comment) { + return; + } + + g_current_comment = eclass; + + g_entityClassComment.text(eclass->comments()); +} + +void SurfaceFlags_setEntityClass(EntityClass *eclass) +{ + if (eclass == g_current_flags) { + return; + } + + g_current_flags = eclass; + + unsigned 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; i < MAX_FLAGS; i++) { + if (eclass->flagnames[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) { + auto widget = ui::CheckButton::from(g_entitySpawnflagsCheck[i]); + auto label = ui::Label::from(gtk_bin_get_child(GTK_BIN(widget))); + label.text(" "); + widget.hide(); + widget.ref(); + g_spawnflagsTable.remove(widget); + } + } + + g_spawnflag_count = spawnflag_count; + + { + for (unsigned int i = 0; (int) i < g_spawnflag_count; ++i) { + auto widget = ui::CheckButton::from(g_entitySpawnflagsCheck[i]); + widget.show(); + + StringOutputStream str(16); + str << LowerCase(eclass->flagnames[spawn_table[i]]); + + g_spawnflagsTable.attach(widget, {i % 4, i % 4 + 1, i / 4, i / 4 + 1}, {GTK_FILL, GTK_FILL}); + widget.unref(); + + auto label = ui::Label::from(gtk_bin_get_child(GTK_BIN(widget))); + label.text(str.c_str()); + } + } +} + +void EntityClassList_selectEntityClass(EntityClass *eclass) +{ + auto 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) { + auto view = ui::TreeView(g_entityClassList); + auto path = gtk_tree_model_get_path(model, &iter); + gtk_tree_selection_select_path(gtk_tree_view_get_selection(view), path); + if (gtk_widget_get_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) +{ + auto row = DialogRow_new(name, attribute.getWidget()); + DialogVBox_packRow(ui::VBox(g_attributeBox), 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)); + m_creators.insert(Creators::value_type("real3", &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(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 << spawn_table[i])); + + toggle_button_set_active_no_signal(ui::ToggleButton::from(g_entitySpawnflagsCheck[i]), v); + } + } + { + // take care of the remaining ones + for (int i = g_spawnflag_count; i < MAX_FLAGS; ++i) { + toggle_button_set_active_no_signal(ui::ToggleButton::from(g_entitySpawnflagsCheck[i]), FALSE); + } + } +} + +void EntityInspector_applySpawnflags() +{ + int f, i, v; + char sz[32]; + + f = 0; + for (i = 0; i < g_spawnflag_count; ++i) { + v = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_entitySpawnflagsCheck[i])); + f |= v << spawn_table[i]; + } + + sprintf(sz, "%i", f); + const char *value = (f == 0) ? "" : sz; + + { + StringOutputStream command; + command << "entitySetFlags -flags " << f; + UndoableCommand undo("entitySetSpawnflags"); + + Scene_EntitySetKeyValue_Selected("spawnflags", value); + } +} + + +void EntityInspector_updateKeyValues() +{ + g_selectedKeyValues.clear(); + g_selectedDefaultKeyValues.clear(); + Entity_GetKeyValues_Selected(g_selectedKeyValues, g_selectedDefaultKeyValues); + + EntityInspector_setEntityClass( + GlobalEntityClassManager().findOrInsert(keyvalues_valueforkey(g_selectedKeyValues, "classname"), false)); + + EntityInspector_updateSpawnflags(); + + ui::ListStore store = g_entprops_store; + + // save current key/val pair around filling epair box + // row_select wipes it and sets to first in list + CopiedString strKey(g_entityKeyEntry.text()); + CopiedString strVal(g_entityValueEntry.text()); + + store.clear(); + // Walk through list and add pairs + for (KeyValues::iterator i = g_selectedKeyValues.begin(); i != g_selectedKeyValues.end(); ++i) { + StringOutputStream key(64); + key << (*i).first.c_str(); + StringOutputStream value(64); + value << (*i).second.c_str(); + store.append(0, key.c_str(), 1, value.c_str()); + } + + g_entityKeyEntry.text(strKey.c_str()); + g_entityValueEntry.text(strVal.c_str()); + + for (EntityAttributes::const_iterator i = g_entityAttributes.begin(); i != g_entityAttributes.end(); ++i) { + (*i)->update(); + } +} + +class EntityInspectorDraw { + IdleDraw m_idleDraw; +public: + EntityInspectorDraw() : m_idleDraw(makeCallbackF(EntityInspector_updateKeyValues)) + { + } + + 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() +{ + auto 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(g_entityClassList), &model, &iter) == FALSE) { + ui::alert(view.window(), "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; + printf(command.c_str()); + printf("\n"); + 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 << gtk_entry_get_text(g_entityKeyEntry); + StringOutputStream value(64); + value << 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")) { + ui::alert(g_entityKeyEntry.window(), "Cannot change \"classname\" key back to worldspawn.", 0, + ui::alert_type::OK); + return; + } + + + // RR2DO2: we don't want spaces in entity keys + if (strstr(key.c_str(), " ")) { + ui::alert(g_entityKeyEntry.window(), "No spaces are allowed in entity keys.", 0, ui::alert_type::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 << 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(ui::TreeSelection 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(ui::Widget widget, GdkEventButton *event, gpointer data) +{ + if (event->type == GDK_2BUTTON_PRESS) { + /* If we're worldspawn - DO NOT ALLOW RENAMING! */ + StringOutputStream value(64); + value << SelectedEntity_getValueForKey("classname"); + + if (!strcmp(value.c_str(), "worldspawn")) { + EntityClassList_createEntity(); + return TRUE; + } + + auto view = g_entityClassList; + GtkTreeModel *model; + GtkTreeIter iter; + if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(g_entityClassList), &model, &iter) == FALSE) { + ui::alert(view.window(), "You must have a selected class to change an entity", "info"); + return FALSE; + } + + char *text; + gtk_tree_model_get(model, &iter, 0, &text, -1); + + StringOutputStream command; + command << "entitySetClass -class " << text; + UndoableCommand undo(command.c_str()); + Scene_EntitySetClassname_Selected(text); + g_free(text); + return TRUE; + } + return FALSE; +} + +static gint EntityClassList_keypress(ui::Widget widget, GdkEventKey *event, gpointer data) +{ + unsigned int code = gdk_keyval_to_upper(event->keyval); + + /* -eukara */ + /*if (event->keyval == GDK_KEY_Return) { + EntityClassList_createEntity(); + return TRUE; + }*/ + + // select the entity that starts with the key pressed + if (code <= 'Z' && code >= 'A') { + auto view = ui::TreeView(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) { + auto path = gtk_tree_model_get_path(model, &iter); + gtk_tree_selection_select_path(gtk_tree_view_get_selection(view), path); + if (gtk_widget_get_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(ui::TreeSelection 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); + + g_entityKeyEntry.text(key); + g_entityValueEntry.text(val); + + g_free(key); + g_free(val); +} + +static void SpawnflagCheck_toggled(ui::Widget widget, gpointer data) +{ + EntityInspector_applySpawnflags(); +} + +static gint EntityEntry_keypress(ui::Entry widget, GdkEventKey *event, gpointer data) +{ + StringOutputStream key(64); + key << gtk_entry_get_text(g_entityKeyEntry); + StringOutputStream value(64); + value << gtk_entry_get_text(g_entityValueEntry); + + if (event->keyval == GDK_KEY_Return) { + if (widget._handle == g_entityKeyEntry._handle) { + g_entityValueEntry.text(""); + gtk_window_set_focus(widget.window(), g_entityValueEntry); + } else { + if (!strcmp(key.c_str(), "classname")) { + ui::alert(widget.window(), "Do not rename classnames. Pick a new entity from the list instead.", "info"); + return FALSE; + } else { + EntityInspector_applyKeyValue(); + } + } + return TRUE; + } + if (event->keyval == GDK_KEY_Escape) { + gtk_window_set_focus(widget.window(), NULL); + return TRUE; + } + + return FALSE; +} + +void EntityInspector_destroyWindow(ui::Widget 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(); +} + +ui::Widget EntityInspector_constructWindow(ui::Window toplevel) +{ + auto hbox = ui::HBox(FALSE, 2); + hbox.show(); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 2); + hbox.connect("destroy", G_CALLBACK(EntityInspector_destroyWindow), 0); + + auto vbox = ui::VBox(FALSE, 2); + vbox.show(); + hbox.pack_start(vbox, FALSE, FALSE, 0); + + { + auto split1 = ui::VPaned(ui::New); + vbox.pack_start(split1, TRUE, TRUE, 0); + split1.show(); + + g_entity_split1 = split1; + + { + ui::Widget split2 = ui::VPaned(ui::New); + gtk_paned_add1(GTK_PANED(split1), split2); + split2.show(); + + g_entity_split2 = split2; + + { + // class list + auto scr = ui::ScrolledWindow(ui::New); + scr.show(); + 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); + + { + ui::ListStore store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER)); + + auto view = ui::TreeView(ui::TreeModel::from(store._handle)); + gtk_tree_view_set_enable_search(view, FALSE); + gtk_tree_view_set_headers_visible(view, FALSE); + view.connect("button_press_event", G_CALLBACK(EntityClassList_button_press), 0); + view.connect("key_press_event", G_CALLBACK(EntityClassList_keypress), 0); + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("Key", renderer, {{"text", 0}}); + gtk_tree_view_append_column(view, column); + } + + { + auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view)); + selection.connect("changed", G_CALLBACK(EntityClassList_selection_changed), 0); + } + + view.show(); + + scr.add(view); + + store.unref(); + g_entityClassList = view; + g_entlist_store = store; + } + } + + /* this is the QUAKEED text */ + { + auto scr = ui::ScrolledWindow(ui::New); + scr.show(); + hbox.pack_start(scr, TRUE, TRUE, 0); + 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); + { + auto text = ui::TextView(ui::New); + text.dimensions(0, -1); // allow shrinking + gtk_text_view_set_wrap_mode(text, GTK_WRAP_WORD); + gtk_text_view_set_editable(text, FALSE); + text.show(); + scr.add(text); + g_entityClassComment = text; + } + } + } + + { + ui::Widget split2 = ui::VPaned(ui::New); + gtk_paned_add2(GTK_PANED(split1), split2); + split2.show(); + + { + auto vbox2 = ui::VBox(FALSE, 2); + vbox2.show(); + gtk_paned_pack1(GTK_PANED(split2), vbox2, FALSE, FALSE); + + { + // Spawnflags (4 colums wide max, or window gets too wide.) + auto table = ui::Table(4, 4, FALSE); + vbox2.pack_start(table, FALSE, TRUE, 0); + table.show(); + + g_spawnflagsTable = table; + + for (int i = 0; i < MAX_FLAGS; i++) { + auto check = ui::CheckButton(""); + check.ref(); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer( + check.connect("toggled", G_CALLBACK(SpawnflagCheck_toggled), 0))); + g_entitySpawnflagsCheck[i] = check; + } + } + + { + // key/value list + auto scr = ui::ScrolledWindow(ui::New); + scr.show(); + vbox2.pack_start(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); + + { + ui::ListStore store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING)); + + auto view = ui::TreeView(ui::TreeModel::from(store._handle)); + gtk_tree_view_set_enable_search(view, FALSE); + gtk_tree_view_set_headers_visible(view, FALSE); + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("", renderer, {{"text", 0}}); + gtk_tree_view_append_column(view, column); + } + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("", renderer, {{"text", 1}}); + gtk_tree_view_append_column(view, column); + } + + { + auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view)); + selection.connect("changed", G_CALLBACK(EntityProperties_selection_changed), 0); + } + + view.show(); + + scr.add(view); + + store.unref(); + + g_entprops_store = store; + } + } + + { + // key/value entry + auto table = ui::Table(2, 2, FALSE); + table.show(); + vbox2.pack_start(table, FALSE, TRUE, 0); + gtk_table_set_row_spacings(table, 3); + gtk_table_set_col_spacings(table, 5); + + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + gtk_widget_set_events(entry, GDK_KEY_PRESS_MASK); + entry.connect("key_press_event", G_CALLBACK(EntityEntry_keypress), 0); + g_entityKeyEntry = entry; + } + + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + gtk_widget_set_events(entry, GDK_KEY_PRESS_MASK); + entry.connect("key_press_event", G_CALLBACK(EntityEntry_keypress), 0); + g_entityValueEntry = entry; + } + + { + auto label = ui::Label("Value"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + + { + auto label = ui::Label("Key"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + } + + { + auto hbox = ui::HBox(TRUE, 4); + hbox.show(); + vbox2.pack_start(hbox, FALSE, TRUE, 0); + + { + auto button = ui::Button("Clear All"); + button.show(); + button.connect("clicked", G_CALLBACK(EntityInspector_clearAllKeyValues), 0); + hbox.pack_start(button, TRUE, TRUE, 0); + } + { + auto button = ui::Button("Delete Key"); + button.show(); + button.connect("clicked", G_CALLBACK(EntityInspector_clearKeyValue), 0); + hbox.pack_start(button, TRUE, TRUE, 0); + } + } + } + + // Let's keep it simple, okay? + /*{ + auto scr = ui::ScrolledWindow(ui::New); + scr.show(); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + auto viewport = ui::Container::from(gtk_viewport_new(0, 0)); + viewport.show(); + gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); + + g_attributeBox = ui::VBox(FALSE, 2); + g_attributeBox.show(); + + viewport.add(g_attributeBox); + scr.add(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 FreeCaller EntityInspectorSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(EntityInspectorSelectionChangedCaller()); + GlobalEntityCreator().setKeyValueChangedFunc(EntityInspector_keyValueChanged); + + // hack + gtk_container_set_focus_chain(GTK_CONTAINER(vbox), NULL); + + return hbox; +} + +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", make_property_string(g_entitysplit1_position)); + GlobalPreferenceSystem().registerPreference("EntitySplit2", make_property_string(g_entitysplit2_position)); + +} + +void EntityInspector_destroy() +{ + GlobalEntityClassManager().detach(g_EntityInspector); +} + +const char *EntityInspector_getCurrentKey() +{ + if (!GroupDialog_isShown()) { + return 0; + } + if (GroupDialog_getPage() != g_page_entity) { + return 0; + } + return gtk_entry_get_text(g_entityKeyEntry); +} diff --git a/radiant/entityinspector.h b/radiant/entityinspector.h new file mode 100644 index 0000000..4b200a8 --- /dev/null +++ b/radiant/entityinspector.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 + */ + +#include + +#if !defined( INCLUDED_ENTITYINSPECTOR_H ) +#define INCLUDED_ENTITYINSPECTOR_H + +ui::Widget EntityInspector_constructWindow(ui::Window parent); + +void EntityInspector_construct(); + +void EntityInspector_destroy(); + +const char *EntityInspector_getCurrentKey(); + +#endif diff --git a/radiant/entitylist.cpp b/radiant/entitylist.cpp new file mode 100644 index 0000000..c13d8d6 --- /dev/null +++ b/radiant/entitylist.cpp @@ -0,0 +1,417 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "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; + + +class EntityList { +public: + enum EDirty { + eDefault, + eSelection, + eInsertRemove, + }; + + EDirty m_dirty; + + IdleDraw m_idleDraw; + WindowPositionTracker m_positionTracker; + + ui::Window m_window; + ui::TreeView m_tree_view{ui::null}; + ui::TreeModel m_tree_model{ui::null}; + bool m_selection_disabled; + + EntityList() : + m_dirty(EntityList::eDefault), + m_idleDraw(RedrawEntityListCaller()), + m_window(ui::null), + m_selection_disabled(false) + { + } + + bool visible() + { + return m_window.visible(); + } +}; + +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(ui::TreeModel 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(ui::TreeViewColumn column, ui::CellRenderer renderer, ui::TreeModel 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, NULL); + + //globalOutputStream() << "rendering cell " << makeQuoted(name) << "\n"; + auto style = gtk_widget_get_style(ui::TreeView(getEntityList().m_tree_view)); + if (instance->childSelected()) { + g_object_set(G_OBJECT(renderer), "cell-background-gdk", &style->base[GTK_STATE_ACTIVE], NULL); + } else { + g_object_set(G_OBJECT(renderer), "cell-background-gdk", &style->base[GTK_STATE_NORMAL], NULL); + } + } else { + gtk_cell_renderer_set_fixed_size(renderer, -1, 0); + g_object_set(G_OBJECT(renderer), "text", "", "visible", FALSE, NULL); + } +} + +static gboolean entitylist_tree_select(ui::TreeSelection selection, ui::TreeModel model, ui::TreePath 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(ui::TreeSelection selection, ui::TreeModel model, ui::TreePath path, + gboolean path_currently_selected, gpointer data) +{ + return TRUE; +} + +void EntityList_ConnectSignals(ui::TreeView view) +{ + auto select = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_select_function(select, reinterpret_cast(entitylist_tree_select), NULL, + 0); +} + +void EntityList_DisconnectSignals(ui::TreeView view) +{ + auto select = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_select_function(select, reinterpret_cast(entitylist_tree_select_null), + 0, 0); +} + + +gboolean treemodel_update_selection(ui::TreeModel model, ui::TreePath path, GtkTreeIter *iter, gpointer data) +{ + auto view = ui::TreeView::from(data); + + scene::Instance *instance; + gtk_tree_model_get_pointer(model, iter, 1, &instance); + Selectable *selectable = Instance_getSelectable(*instance); + + if (selectable != 0) { + auto 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(ui::TreeModel model, ui::TreeView view) +{ + EntityList_DisconnectSignals(view); + gtk_tree_model_foreach(model, reinterpret_cast(treemodel_update_selection), view._handle); + EntityList_ConnectSignals(view); +} + + +void RedrawEntityList() +{ + switch (getEntityList().m_dirty) { + case EntityList::eInsertRemove: + case EntityList::eSelection: + EntityList_UpdateSelection(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(ui::TreeView view, GtkTreeIter *iter, ui::TreePath path, gpointer user_data) +{ +} + +void entitylist_treeview_row_expanded(ui::TreeView view, GtkTreeIter *iter, ui::TreePath path, gpointer user_data) +{ + EntityList_SelectionUpdate(); +} + + +void EntityList_SetShown(bool shown) +{ + getEntityList().m_window.visible(shown); +} + +void EntityList_toggleShown() +{ + EntityList_SetShown(!getEntityList().visible()); +} + +gint graph_tree_model_compare_name(ui::TreeModel 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 = ui::TreeModel::from(scene_graph_get_tree_model()); + + gtk_tree_view_set_model(getEntityList().m_tree_view, getEntityList().m_tree_model); +} + +void DetachEntityTreeModel() +{ + getEntityList().m_tree_model = ui::TreeModel(ui::null); + + gtk_tree_view_set_model(getEntityList().m_tree_view, 0); +} + +void EntityList_constructWindow(ui::Window main_window) +{ + ASSERT_TRUE(!getEntityList().m_window); + + auto window = ui::Window(create_persistent_floating_window("Entity List", main_window)); + + window.add_accel_group(global_accel); + + getEntityList().m_positionTracker.connect(window); + + + getEntityList().m_window = window; + + { + auto scr = create_scrolled_window(ui::Policy::AUTOMATIC, ui::Policy::AUTOMATIC); + window.add(scr); + + { + auto view = ui::TreeView(ui::New); + gtk_tree_view_set_headers_visible(view, FALSE); + + auto renderer = ui::CellRendererText(ui::New); + auto 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, + reinterpret_cast(entitylist_treeviewcolumn_celldatafunc), + 0, 0); + + auto select = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE); + + view.connect("row_expanded", G_CALLBACK(entitylist_treeview_row_expanded), 0); + view.connect("row_collapsed", G_CALLBACK(entitylist_treeview_rowcollapsed), 0); + + gtk_tree_view_append_column(view, column); + + view.show(); + scr.add(view); + getEntityList().m_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", make_property( + getEntityList().m_positionTracker)); + + typedef FreeCaller 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 0000000..38ee587 --- /dev/null +++ b/radiant/entitylist.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 + */ + +#include + +#if !defined( INCLUDED_ENTITYLIST_H ) +#define INCLUDED_ENTITYLIST_H + +void EntityList_Construct(); + +void EntityList_Destroy(); + +void EntityList_constructWindow(ui::Window main_window); + +void EntityList_destroyWindow(); + +void EntityList_toggleShown(); + +#endif diff --git a/radiant/environment.cpp b/radiant/environment.cpp new file mode 100644 index 0000000..b817011 --- /dev/null +++ b/radiant/environment.cpp @@ -0,0 +1,286 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.h" + +#include "stream/textstream.h" +#include "string/string.h" +#include "stream/stringstream.h" +#include "debugging/debugging.h" +#include "os/path.h" +#include "os/file.h" +#include "cmdlib.h" + +int g_argc; +char const **g_argv; + +void args_init(int argc, char const *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; +} + +char const *gamedetect_argv_buffer[1024]; + +void gamedetect_found_game(char const *game, char *path) +{ + int argc; + static char buf[128]; + + if (g_argv == gamedetect_argv_buffer) { + return; + } + + globalOutputStream() << "Detected game " << game << " in " << path << "\n"; + + sprintf(buf, "-%s-EnginePath", game); + argc = 0; + gamedetect_argv_buffer[argc++] = "-global-gamefile"; + gamedetect_argv_buffer[argc++] = game; + gamedetect_argv_buffer[argc++] = buf; + gamedetect_argv_buffer[argc++] = path; + if ((size_t) (argc + g_argc) >= sizeof(gamedetect_argv_buffer) / sizeof(*gamedetect_argv_buffer) - 1) { + g_argc = sizeof(gamedetect_argv_buffer) / sizeof(*gamedetect_argv_buffer) - g_argc - 1; + } + memcpy(gamedetect_argv_buffer + 4, g_argv, sizeof(*gamedetect_argv_buffer) * g_argc); + g_argc += argc; + g_argv = gamedetect_argv_buffer; +} + +bool gamedetect_check_game(char const *gamefile, const char *checkfile1, const char *checkfile2, + char *buf /* must have 64 bytes free after bufpos */, int bufpos) +{ + buf[bufpos] = '/'; + + strcpy(buf + bufpos + 1, checkfile1); + globalOutputStream() << "Checking for a game file in " << buf << "\n"; + if (!file_exists(buf)) { + return false; + } + + if (checkfile2) { + strcpy(buf + bufpos + 1, checkfile2); + globalOutputStream() << "Checking for a game file in " << buf << "\n"; + if (!file_exists(buf)) { + return false; + } + } + + buf[bufpos + 1] = 0; + gamedetect_found_game(gamefile, buf); + return true; +} + +void gamedetect() +{ + // if we're inside a Nexuiz install + // default to nexuiz.game (unless the user used an option to inhibit this) + bool nogamedetect = false; + int i; + for (i = 1; i < g_argc - 1; ++i) { + if (g_argv[i][0] == '-') { + if (!strcmp(g_argv[i], "-gamedetect")) { + nogamedetect = !strcmp(g_argv[i + 1], "false"); + } + ++i; + } + } + if (!nogamedetect) { + static char buf[1024 + 64]; + strncpy(buf, environment_get_app_path(), sizeof(buf)); + buf[sizeof(buf) - 1 - 64] = 0; + if (!strlen(buf)) { + return; + } + + char *p = buf + strlen(buf) - 1; // point directly on the slash of get_app_path + while (p != buf) { + // we found nothing + // go backwards + --p; + while (p != buf && *p != '/' && *p != '\\') { + --p; + } + } + } +} + +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(); +} + +bool portable_app_setup() +{ + StringOutputStream confdir(256); + confdir << app_path.c_str() << "settings/"; + if (file_exists(confdir.c_str())) { + home_path = confdir.c_str(); + return true; + } + return false; +} + +#if GDEF_OS_POSIX + +#include +#include +#include + +#include + +const char *LINK_NAME = +#if GDEF_OS_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 const *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 const *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); + + { + char real[PATH_MAX]; + app_path = getexename(real); + ASSERT_MESSAGE(!string_empty(app_path.c_str()), "failed to deduce app path"); + } + + if (!portable_app_setup()) { + StringOutputStream home(256); + home << DirectoryCleaned(g_get_user_data_dir()) << "worldspawn/"; + Q_mkdir(home.c_str()); + home_path = home.c_str(); + } + gamedetect(); +} + +#elif GDEF_OS_WINDOWS + +#include + +void environment_init( int argc, char const* argv[] ){ + args_init( argc, argv ); + + // 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(); + + if ( !portable_app_setup() ) { + char *appdata = getenv( "APPDATA" ); + StringOutputStream home( 256 ); + if ( !appdata || string_empty( appdata ) ) { + ERROR_MESSAGE( "Application Data folder not available.\n" + "WorldSpawn will use C:\\ for user preferences.\n" ); + home << "C:"; + } else { + home << PathCleaned( appdata ); + } + home << "/WorldSpawnSettings/"; + Q_mkdir( home.c_str() ); + home_path = home.c_str(); + } + gamedetect(); +} + +#else +#error "unsupported platform" +#endif diff --git a/radiant/environment.h b/radiant/environment.h new file mode 100644 index 0000000..173c2ab --- /dev/null +++ b/radiant/environment.h @@ -0,0 +1,34 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 const *argv[]); + +const char *environment_get_home_path(); + +const char *environment_get_app_path(); + +extern int g_argc; +extern char const **g_argv; + +#endif diff --git a/radiant/error.cpp b/radiant/error.cpp new file mode 100644 index 0000000..375a9dd --- /dev/null +++ b/radiant/error.cpp @@ -0,0 +1,139 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "error.h" +#include "globaldefs.h" + +#include "debugging/debugging.h" +#include "igl.h" + +#include "gtkutil/messagebox.h" +#include "console.h" +#include "preferences.h" + + +#if GDEF_OS_WINDOWS +#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"); + +#if GDEF_OS_WINDOWS + 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 0000000..43acdb5 --- /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 0000000..00b0023 --- /dev/null +++ b/radiant/feedback.cpp @@ -0,0 +1,334 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +#include "debugging/debugging.h" + +#include "igl.h" +#include "iselection.h" + +#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(ui::TreeSelection selection, gpointer data) +{ + g_DbgDlg.DropHighlight(); + + GtkTreeModel *model; + GtkTreeIter selected; + if (gtk_tree_selection_get_selected(selection, &model, &selected)) { + auto 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) { + m_clist.clear(); + } +} + +void CDbgDlg::Push(ISAXHandler *pHandler) +{ + // push in the list + g_ptr_array_add(m_pFeedbackElements, (void *) pHandler); + + if (!GetWidget()) { + Create(); + } + + // put stuff in the list + m_clist.clear(); + for (std::size_t i = 0; i < static_cast( m_pFeedbackElements->len ); ++i) { + m_clist.append(0, GetElement(i)->getName()); + } + + ShowDlg(); +} + +ui::Window CDbgDlg::BuildDialog() +{ + auto window = MainFrame_getWindow().create_floating_window("Q3Map debug window"); + + auto scr = ui::ScrolledWindow(ui::New); + scr.show(); + window.add(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); + + { + ui::ListStore store = ui::ListStore::from(gtk_list_store_new(1, G_TYPE_STRING)); + + auto view = ui::TreeView(ui::TreeModel::from(store._handle)); + gtk_tree_view_set_headers_visible(view, FALSE); + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("", renderer, {{"text", 0}}); + gtk_tree_view_append_column(view, column); + } + + { + auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + selection.connect("changed", G_CALLBACK(feedback_selection_changed), NULL); + } + + view.show(); + + scr.add(view); + + store.unref(); + + m_clist = store; + } + + return window; +} diff --git a/radiant/feedback.h b/radiant/feedback.h new file mode 100644 index 0000000..cdb835a --- /dev/null +++ b/radiant/feedback.h @@ -0,0 +1,241 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 "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: + virtual ~IGL2DWindow() = default; + +// 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); +}; + + +class CDbgDlg : public Dialog { + GPtrArray *m_pFeedbackElements; +// the list widget we use in the dialog + ui::ListStore m_clist{ui::null}; + 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()) { + Destroy(); + } + } +// void HideDlg(); +protected: + ui::Window 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 0000000..d8e9cb3 --- /dev/null +++ b/radiant/filetypes.cpp @@ -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 + */ + +#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_can_load(other.can_load), m_can_import(other.can_import), m_can_save(other.can_save), + 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(), m_can_load, m_can_save, m_can_import); + } + + bool m_can_load; + bool m_can_import; + bool m_can_save; + 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, bool want_load, bool want_import, bool want_save) + { + filetype_list_t &list_ref = m_typelists[moduleType]; + for (filetype_list_t::iterator i = list_ref.begin(); i != list_ref.end(); ++i) { + if (want_load && !(*i).m_can_load) { + return; + } + if (want_import && !(*i).m_can_import) { + return; + } + if (want_save && !(*i).m_can_save) { + return; + } + 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 0000000..60cabca --- /dev/null +++ b/radiant/filetypes.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_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 0000000..2dd0f6a --- /dev/null +++ b/radiant/filters.cpp @@ -0,0 +1,307 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 Callback &importCallback) + { + importCallback((g_filters_globals.exclude & m_mask) != 0); + } + + typedef MemberCaller &), &ToggleFilterFlag::active> 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(ui::Menu menu_in_menu) +{ + create_check_menu_item_with_mnemonic(menu_in_menu, "World", "FilterWorldBrushes"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Groups", "FilterGroupBrushes"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Entities", "FilterEntities"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Areaportals", "FilterAreaportals"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Translucent", "FilterTranslucent"); + 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"); + 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"); + 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"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Botclips", "FilterBotClips"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Decals", "FilterDecals"); + + // 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", make_property_string(g_filters_globals.exclude)); + + GlobalCommands_insert("InvertFilters", makeCallbackF(InvertFilters)); + GlobalCommands_insert("ResetFilters", makeCallbackF(ResetFilters)); + + add_filter_command(EXCLUDE_WORLD, "FilterWorldBrushes", Accelerator('1', (GdkModifierType) GDK_MOD1_MASK)); + add_filter_command(EXCLUDE_GROUP, "FilterGroupBrushes", accelerator_null()); + 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)); + add_filter_command(EXCLUDE_DECALS, "FilterDecals", Accelerator('D', (GdkModifierType) GDK_SHIFT_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 0000000..12c71cd --- /dev/null +++ b/radiant/filters.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 + */ + +#include + +#if !defined( INCLUDED_FILTERS_H ) +#define INCLUDED_FILTERS_H + +void Filters_constructMenu(ui::Menu menu_in_menu); + +#endif diff --git a/radiant/findtexturedialog.cpp b/radiant/findtexturedialog.cpp new file mode 100644 index 0000000..2a6c028 --- /dev/null +++ b/radiant/findtexturedialog.cpp @@ -0,0 +1,284 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +#include "debugging/debugging.h" + +#include "ishaders.h" + +#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 ShowCaller; + + static void updateTextures(const char *name); + + FindTextureDialog(); + + virtual ~FindTextureDialog(); + + ui::Window BuildDialog(); + + void constructWindow(ui::Window 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(ui::Widget widget, gpointer data) + { + g_FindTextureDialog.exportData(); + FindTextureDialog_apply(); + } + + static void OnFind(ui::Widget widget, gpointer data) + { + g_FindTextureDialog.exportData(); + FindTextureDialog_apply(); + } + + static void OnOK(ui::Widget widget, gpointer data) + { + g_FindTextureDialog.exportData(); + FindTextureDialog_apply(); + g_FindTextureDialog.HideDlg(); + } + + static void OnClose(ui::Widget widget, gpointer data) + { + g_FindTextureDialog.HideDlg(); + } + + + static gint find_focus_in(ui::Widget widget, GdkEventFocus *event, gpointer data) + { + g_bFindActive = true; + return FALSE; + } + + static gint replace_focus_in(ui::Widget widget, GdkEventFocus *event, gpointer data) + { + g_bFindActive = false; + return FALSE; + } +} + +// ============================================================================= +// FindTextureDialog class + +FindTextureDialog::FindTextureDialog() +{ + m_bSelectedOnly = FALSE; +} + +FindTextureDialog::~FindTextureDialog() +{ +} + +ui::Window FindTextureDialog::BuildDialog() +{ + ui::Widget label{ui::null}; + ui::Widget button{ui::null}; + ui::Entry entry{ui::null}; + + auto dlg = ui::Window(create_floating_window("Find / Replace Texture(s)", m_parent)); + + auto hbox = ui::HBox(FALSE, 5); + hbox.show(); + dlg.add(hbox); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 5); + + auto vbox = ui::VBox(FALSE, 5); + vbox.show(); + hbox.pack_start(vbox, TRUE, TRUE, 0); + + auto table = ui::Table(2, 2, FALSE); + table.show(); + vbox.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + + label = ui::Label("Find:"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + label = ui::Label("Replace:"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + + entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + entry.connect("focus_in_event", + G_CALLBACK(find_focus_in), 0); + AddDialogData(entry, m_strFind); + GlobalTextureEntryCompletion::instance().connect(entry); + + entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + entry.connect("focus_in_event", + G_CALLBACK(replace_focus_in), 0); + AddDialogData(entry, m_strReplace); + GlobalTextureEntryCompletion::instance().connect(entry); + + auto check = ui::CheckButton("Within selected brushes only"); + check.show(); + vbox.pack_start(check, TRUE, TRUE, 0); + AddDialogData(check, m_bSelectedOnly); + + vbox = ui::VBox(FALSE, 5); + vbox.show(); + hbox.pack_start(vbox, FALSE, FALSE, 0); + + button = ui::Button("Apply"); + button.show(); + vbox.pack_start(button, FALSE, FALSE, 0); + button.connect("clicked", + G_CALLBACK(OnApply), 0); + button.dimensions(60, -1); + + button = ui::Button("Close"); + button.show(); + vbox.pack_start(button, FALSE, FALSE, 0); + button.connect("clicked", + G_CALLBACK(OnClose), 0); + button.dimensions(60, -1); + + return dlg; +} + +void FindTextureDialog::updateTextures(const char *name) +{ + if (isOpen()) { + if (g_bFindActive) { + setFindStr(name + 9); + } else { + setReplaceStr(name + 9); + } + } +} + +bool FindTextureDialog::isOpen() +{ + return g_FindTextureDialog.GetWidget().visible(); +} + +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(ui::Window 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 0000000..a367c15 --- /dev/null +++ b/radiant/findtexturedialog.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#if !defined( INCLUDED_FINDTEXTUREDIALOG_H ) +#define INCLUDED_FINDTEXTUREDIALOG_H + +void FindTextureDialog_Construct(); + +void FindTextureDialog_Destroy(); + +void FindTextureDialog_constructWindow(ui::Window 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 0000000..7d2904a --- /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 0000000..0adad26 --- /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 0000000..0c6fd8a --- /dev/null +++ b/radiant/grid.cpp @@ -0,0 +1,302 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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); + +bool g_grid_snap = true; + +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 GetSnapGridSize() +{ + return g_grid_snap ? g_gridsize : 0; +} + +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 Callback &importCallback) + { + importCallback(g_grid_power == m_id); + } + + typedef MemberCaller &), &GridMenuItem::active> 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_grid_snap = true; + 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() +{ + g_grid_snap = true; + if (g_grid_power > GRIDPOWER_0125) { + setGridPower(static_cast( --g_grid_power )); + } +} + +void GridNext() +{ + g_grid_snap = true; + if (g_grid_power < GRIDPOWER_256) { + setGridPower(static_cast( ++g_grid_power )); + } +} + +void ToggleGridSnap() +{ + g_grid_snap = !g_grid_snap; + GridChangeNotify(); +} + +void Grid_registerCommands() +{ + GlobalCommands_insert("GridDown", makeCallbackF(GridPrev), Accelerator('[')); + GlobalCommands_insert("GridUp", makeCallbackF(GridNext), Accelerator(']')); + + GlobalCommands_insert("ToggleGridSnap", makeCallbackF(ToggleGridSnap)); + + 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(ui::Menu 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"); + command_connect_accelerator("ToggleGridSnap"); +} + +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(makeCallbackF(Grid_constructPage)); +} + +void Grid_construct() +{ + Grid_registerPreferencesPage(); + + g_grid_default = GridDefault_forGridPower(GRIDPOWER_8); + + GlobalPreferenceSystem().registerPreference("GridDefault", make_property_string(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 0000000..513f4a0 --- /dev/null +++ b/radiant/grid.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "signal/signalfwd.h" + +float GetSnapGridSize(); + +float GetGridSize(); + +int Grid_getPower(); + +void AddGridChangeCallback(const SignalHandler &handler); + +void Grid_registerCommands(); + +void Grid_constructMenu(ui::Menu 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 0000000..0da79fb --- /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 "globaldefs.h" + +#include "debugging/debugging.h" + +#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 "gtkutil/window.h" + +class GroupDlg { +public: + ui::Widget m_pNotebook{ui::null}; + ui::Window m_window{ui::null}; + + GroupDlg(); + + void Create(ui::Window 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); + m_window.show(); + } + + void Hide() + { + m_window.hide(); + } + + WindowPositionTracker m_position_tracker; +}; + +namespace { + GroupDlg g_GroupDlg; + + std::size_t g_current_page; + std::vector &)>> g_pages; +} + +void GroupDialog_updatePageTitle(ui::Window window, std::size_t pageIndex) +{ + if (pageIndex < g_pages.size()) { + g_pages[pageIndex](PointerCaller(window)); + } +} + +static gboolean switch_page(GtkNotebook *notebook, gpointer page, guint page_num, gpointer data) +{ + GroupDialog_updatePageTitle(ui::Window::from(data), page_num); + g_current_page = page_num; + + return FALSE; +} + +GroupDlg::GroupDlg() : m_window(ui::null) +{ + m_position_tracker.setPosition(c_default_window_pos); +} + +void GroupDlg::Create(ui::Window parent) +{ + ASSERT_MESSAGE(!m_window, "dialog already created"); + + auto window = ui::Window(create_persistent_floating_window("Entities", parent)); + + global_accel_connect_window(window); + + window_connect_focus_in_clear_focus_widget(window); + + m_window = window; + +#if GDEF_OS_WINDOWS + 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); + + { + ui::Widget notebook = ui::Widget::from(gtk_notebook_new()); + notebook.show(); + window.add(notebook); + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_BOTTOM); + m_pNotebook = notebook; + + notebook.connect("switch_page", G_CALLBACK(switch_page), (gpointer) window); + } +} + + +ui::Widget GroupDialog_addPage(const char *tabLabel, ui::Widget widget, + const Callback &)> &title) +{ + ui::Widget w = ui::Label(tabLabel); + w.show(); + auto page = ui::Widget::from(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 g_GroupDlg.m_window.visible(); +} + +void GroupDialog_setShown(bool shown) +{ + shown ? g_GroupDlg.Show() : g_GroupDlg.Hide(); +} + +void GroupDialog_ToggleShow() +{ + GroupDialog_setShown(!GroupDialog_isShown()); +} + +void GroupDialog_constructWindow(ui::Window main_window) +{ + g_GroupDlg.Create(main_window); +} + +void GroupDialog_destroyWindow() +{ + ASSERT_TRUE(g_GroupDlg.m_window); + destroy_floating_window(g_GroupDlg.m_window); + g_GroupDlg.m_window = ui::Window{ui::null}; +} + + +ui::Window GroupDialog_getWindow() +{ + return ui::Window(g_GroupDlg.m_window); +} + +void GroupDialog_show() +{ + g_GroupDlg.Show(); +} + +ui::Widget GroupDialog_getPage() +{ + return ui::Widget::from(gtk_notebook_get_nth_page(GTK_NOTEBOOK(g_GroupDlg.m_pNotebook), gint(g_current_page))); +} + +void GroupDialog_setPage(ui::Widget 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(ui::Widget page) +{ + if (GroupDialog_getPage() == page) { + GroupDialog_ToggleShow(); + } else { + g_GroupDlg.m_window.show(); + 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(ui::Widget page) +{ + if (GroupDialog_getPage() == page) { + GroupDialog_updatePageTitle(g_GroupDlg.m_window, g_current_page); + } +} + + +#include "preferencesystem.h" + +void GroupDialog_Construct() +{ + GlobalPreferenceSystem().registerPreference("EntityWnd", make_property( + g_GroupDlg.m_position_tracker)); + + GlobalCommands_insert("ViewEntityInfo", makeCallbackF(GroupDialog_ToggleShow), Accelerator('N')); +} + +void GroupDialog_Destroy() +{ +} diff --git a/radiant/groupdialog.h b/radiant/groupdialog.h new file mode 100644 index 0000000..63f36a0 --- /dev/null +++ b/radiant/groupdialog.h @@ -0,0 +1,59 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "property.h" +#include "generic/callback.h" + +void GroupDialog_Construct(); + +void GroupDialog_Destroy(); + +void GroupDialog_constructWindow(ui::Window main_window); + +void GroupDialog_destroyWindow(); + +ui::Window GroupDialog_getWindow(); + +void GroupDialog_show(); + +inline void RawStringExport(const char *string, const Callback &importer) +{ + importer(string); +} + +typedef ConstPointerCaller &), RawStringExport> RawStringExportCaller; + +ui::Widget GroupDialog_addPage(const char *tabLabel, ui::Widget widget, + const Callback &)> &title); + +void GroupDialog_showPage(ui::Widget page); + +void GroupDialog_updatePageTitle(ui::Widget page); + +bool GroupDialog_isShown(); + +ui::Widget GroupDialog_getPage(); + +#endif diff --git a/radiant/gtkdlgs.cpp b/radiant/gtkdlgs.cpp new file mode 100644 index 0000000..adf5e7d --- /dev/null +++ b/radiant/gtkdlgs.cpp @@ -0,0 +1,1024 @@ +/* + 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 "globaldefs.h" + +#include + +#include "debugging/debugging.h" +#include "version.h" +#include "aboutmsg.h" + +#include "igl.h" +#include "iscenegraph.h" +#include "iselection.h" + +#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 { + ui::ComboBoxText game_select{ui::null}; + ui::Entry fsgame_entry{ui::null}; +}; + +gboolean OnSelchangeComboWhatgame(ui::Widget 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); + + combo->fsgame_entry.text(gamecombo.fs_game); + gtk_widget_set_sensitive(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; + ui::ComboBox gamemode_combo{ui::null}; +}; + +ui::Window ProjectSettingsDialog_construct(ProjectSettingsDialog &dialog, ModalDialog &modal) +{ + auto window = MainFrame_getWindow().create_dialog_window("Project Settings", G_CALLBACK(dialog_delete_callback), + &modal); + + { + auto table1 = create_dialog_table(1, 2, 4, 4, 4); + window.add(table1); + { + auto vbox = create_dialog_vbox(4); + table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL}); + { + auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &modal); + vbox.pack_start(button, FALSE, FALSE, 0); + } + { + auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &modal); + vbox.pack_start(button, FALSE, FALSE, 0); + } + } + { + auto frame = create_dialog_frame("Project settings"); + table1.attach(frame, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, GTK_FILL}); + { + auto table2 = create_dialog_table((globalMappingMode().do_mapping_mode) ? 4 : 3, 2, 4, 4, 4); + frame.add(table2); + + { + auto label = ui::Label("Select mod"); + label.show(); + table2.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + } + { + dialog.game_combo.game_select = ui::ComboBoxText(ui::New); + + gtk_combo_box_text_append_text(dialog.game_combo.game_select, + globalGameComboConfiguration().basegame); + if (globalGameComboConfiguration().known[0] != '\0') { + gtk_combo_box_text_append_text(dialog.game_combo.game_select, + globalGameComboConfiguration().known); + } + gtk_combo_box_text_append_text(dialog.game_combo.game_select, + globalGameComboConfiguration().custom); + + dialog.game_combo.game_select.show(); + table2.attach(dialog.game_combo.game_select, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + + dialog.game_combo.game_select.connect("changed", G_CALLBACK(OnSelchangeComboWhatgame), + &dialog.game_combo); + } + + { + auto label = ui::Label("fs_game"); + label.show(); + table2.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table2.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + + dialog.game_combo.fsgame_entry = entry; + } + + if (globalMappingMode().do_mapping_mode) { + auto label = ui::Label("Mapping mode"); + label.show(); + table2.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + auto combo = ui::ComboBoxText(ui::New); + gtk_combo_box_text_append_text(combo, globalMappingMode().sp_mapping_mode); + gtk_combo_box_text_append_text(combo, globalMappingMode().mp_mapping_mode); + + combo.show(); + table2.attach(combo, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 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); + dialog.game_combo.fsgame_entry.text(gamecombo.fs_game); + gtk_widget_set_sensitive(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; + + ui::Window window = ProjectSettingsDialog_construct(dialog, modal); + + if (modal_dialog_show(window, modal) == eIDOK) { + ProjectSettingsDialog_ok(dialog); + } + + window.destroy(); + } +} + +// ============================================================================= +// Arbitrary Sides dialog + +void DoSides(int type, int axis) +{ + ModalDialog dialog; + + auto window = MainFrame_getWindow().create_dialog_window("Arbitrary sides", G_CALLBACK(dialog_delete_callback), + &dialog); + + auto accel = ui::AccelGroup(ui::New); + window.add_accel_group(accel); + + auto sides_entry = ui::Entry(ui::New); + { + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + { + auto label = ui::Label("Sides:"); + label.show(); + hbox.pack_start(label, FALSE, FALSE, 0); + } + { + auto entry = sides_entry; + entry.show(); + hbox.pack_start(entry, FALSE, FALSE, 0); + gtk_widget_grab_focus(entry); + } + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_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())); + } + + window.destroy(); +} + +// ============================================================================= +// About dialog (no program is complete without one) +void DoAbout() +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + + auto window = MainFrame_getWindow().create_modal_dialog_window("About WorldSpawn", dialog); + + { + auto vbox = create_dialog_vbox(4, 4); + window.add(vbox); + + { + auto hbox = create_dialog_hbox(4); + vbox.pack_start(hbox, FALSE, TRUE, 0); + + { + auto vbox2 = create_dialog_vbox(4); + hbox.pack_start(vbox2, TRUE, FALSE, 0); + { + auto frame = create_dialog_frame(0, ui::Shadow::IN); + vbox2.pack_start(frame, FALSE, FALSE, 0); + { + auto image = new_local_image("logo.xpm"); + image.show(); + frame.add(image); + } + } + } + + { + char const *label_text = "WorldSpawn " WorldSpawn_VERSION "\n" + __DATE__ "\n\n" + "QER taken to the space age.\n\n" + "Developed by Vera Visions LLC.\n" + "Programming by Marco Hladik\n" + "Licensed by id software\n\n" + "Copyright (C) 2015-2020"; + + auto label = ui::Label(label_text); + + label.show(); + hbox.pack_start(label, FALSE, FALSE, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + } + + { + auto vbox2 = create_dialog_vbox(4); + hbox.pack_start(vbox2, FALSE, TRUE, 0); + { + auto button = create_modal_dialog_button("OK", ok_button); + vbox2.pack_start(button, FALSE, FALSE, 0); + } + } + } + { + auto frame = create_dialog_frame("OpenGL Properties"); + vbox.pack_start(frame, FALSE, FALSE, 0); + { + auto table = create_dialog_table(3, 2, 4, 4, 4); + frame.add(table); + { + auto label = ui::Label("Vendor:"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label("Version:"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label("Renderer:"); + label.show(); + table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label(reinterpret_cast( glGetString(GL_VENDOR))); + label.show(); + table.attach(label, {1, 2, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label(reinterpret_cast( glGetString(GL_VERSION))); + label.show(); + table.attach(label, {1, 2, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label(reinterpret_cast( glGetString(GL_RENDERER))); + label.show(); + table.attach(label, {1, 2, 2, 3}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + } + { + auto frame = create_dialog_frame("OpenGL Extensions"); + vbox.pack_start(frame, TRUE, TRUE, 0); + { + auto sc_extensions = create_scrolled_window(ui::Policy::AUTOMATIC, ui::Policy::ALWAYS, 4); + frame.add(sc_extensions); + { + auto text_extensions = ui::TextView(ui::New); + gtk_text_view_set_editable(text_extensions, FALSE); + sc_extensions.add(text_extensions); + text_extensions.text(reinterpret_cast(glGetString(GL_EXTENSIONS))); + gtk_text_view_set_wrap_mode(text_extensions, GTK_WRAP_WORD); + text_extensions.show(); + } + } + } + } + } + + modal_dialog_show(window, dialog); + + window.destroy(); +} + +// ============================================================================= +// TextureLayout dialog + +// Last used texture scale values +static float last_used_texture_layout_scale_x = 4.0; +static float last_used_texture_layout_scale_y = 4.0; + +EMessageBoxReturn DoTextureLayout(float *fx, float *fy) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + ui::Entry x{ui::null}; + ui::Entry y{ui::null}; + + auto window = MainFrame_getWindow().create_modal_dialog_window("Patch texture layout", dialog); + + auto accel = ui::AccelGroup(ui::New); + window.add_accel_group(accel); + + { + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + auto label = ui::Label("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."); + label.show(); + vbox.pack_start(label, TRUE, TRUE, 0); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + } + { + auto table = create_dialog_table(2, 2, 4, 4); + table.show(); + vbox.pack_start(table, TRUE, TRUE, 0); + { + auto label = ui::Label("Texture x:"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label("Texture y:"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + + x = entry; + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + + y = entry; + } + } + } + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, FALSE, FALSE, 0); + { + auto button = create_modal_dialog_button("OK", ok_button); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_modal_dialog_button("Cancel", cancel_button); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + } + } + + // Initialize with last used values + char buf[16]; + + sprintf(buf, "%f", last_used_texture_layout_scale_x); + x.text(buf); + + sprintf(buf, "%f", last_used_texture_layout_scale_y); + y.text(buf); + + // Set focus after intializing the values + gtk_widget_grab_focus(x); + + 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))); + + // Remember last used values + last_used_texture_layout_scale_x = *fx; + last_used_texture_layout_scale_y = *fy; + } + + window.destroy(); + + return ret; +} + +// ============================================================================= +// Text Editor dialog + +// master window widget +static ui::Window text_editor{ui::null}; +static ui::Widget text_widget{ui::null}; // slave, text widget from the gtk editor + +static gint editor_delete(ui::Widget widget, gpointer data) +{ + if (ui::alert(widget.window(), "Close the shader editor ?", "V-Editor", ui::alert_type::YESNO, + ui::alert_icon::Question) == ui::alert_response::NO) { + return TRUE; + } + + text_editor.hide(); + + return TRUE; +} + +static void editor_save(ui::Widget 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) { + ui::alert(ui::Widget::from(data).window(), "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(ui::Widget widget, gpointer data) +{ + if (ui::alert(text_editor.window(), "Close the shader editor ?", "V-Editor", ui::alert_type::YESNO, + ui::alert_icon::Question) == ui::alert_response::NO) { + return; + } + + text_editor.hide(); +} + +static void CreateGtkTextEditor() +{ + auto dlg = ui::Window(ui::window_type::TOP); + + dlg.connect("delete_event", + G_CALLBACK(editor_delete), 0); + gtk_window_set_default_size(dlg, 600, 300); + + auto vbox = ui::VBox(FALSE, 5); + vbox.show(); + dlg.add(vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); + + auto scr = ui::ScrolledWindow(ui::New); + scr.show(); + vbox.pack_start(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); + + auto text = ui::TextView(ui::New); + scr.add(text); + text.show(); + g_object_set_data(G_OBJECT(dlg), "text", (gpointer) text); + gtk_text_view_set_editable(text, TRUE); + + auto hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox.pack_start(hbox, FALSE, TRUE, 0); + + auto button = ui::Button("Close"); + button.show(); + hbox.pack_end(button, FALSE, FALSE, 0); + button.connect("clicked", + G_CALLBACK(editor_close), dlg); + button.dimensions(60, -1); + + button = ui::Button("Save"); + button.show(); + hbox.pack_end(button, FALSE, FALSE, 0); + button.connect("clicked", + G_CALLBACK(editor_save), dlg); + button.dimensions(60, -1); + + 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"; + text_editor.hide(); + } 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(text_editor, filename); + + auto text_buffer = gtk_text_view_get_buffer(ui::TextView::from(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 + text_editor.show(); + +#if GDEF_OS_WINDOWS + ui::process(); +#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); + } + +#if GDEF_OS_WINDOWS + gtk_widget_queue_draw( text_widget ); +#endif + + free(buf); + fclose(f); + } +} + +// ============================================================================= +// Light Intensity dialog + +EMessageBoxReturn DoLightIntensityDlg(int *intensity) +{ + ModalDialog dialog; + ui::Entry intensity_entry{ui::null}; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + + ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Light intensity", dialog, -1, -1); + + auto accel_group = ui::AccelGroup(ui::New); + window.add_accel_group(accel_group); + + { + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + auto label = ui::Label("ESC for default, ENTER to validate"); + label.show(); + vbox.pack_start(label, FALSE, FALSE, 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + vbox.pack_start(entry, TRUE, TRUE, 0); + + gtk_widget_grab_focus(entry); + + intensity_entry = entry; + } + } + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, FALSE, FALSE, 0); + + { + auto button = create_modal_dialog_button("OK", ok_button); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Return, (GdkModifierType) 0, + GTK_ACCEL_VISIBLE); + } + { + auto button = create_modal_dialog_button("Cancel", cancel_button); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType) 0, + GTK_ACCEL_VISIBLE); + } + } + } + + char buf[16]; + sprintf(buf, "%d", *intensity); + intensity_entry.text(buf); + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + if (ret == eIDOK) { + *intensity = atoi(gtk_entry_get_text(intensity_entry)); + } + + window.destroy(); + + return ret; +} + +// ============================================================================= +// Add new shader tag dialog + +EMessageBoxReturn DoShaderTagDlg(CopiedString *tag, const char *title) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + + auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1); + + auto accel_group = ui::AccelGroup(ui::New); + window.add_accel_group(accel_group); + + auto textentry = ui::Entry(ui::New); + { + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces")); + auto label = ui::Label("ESC to cancel, ENTER to validate"); + label.show(); + vbox.pack_start(label, FALSE, FALSE, 0); + } + { + auto entry = textentry; + entry.show(); + vbox.pack_start(entry, TRUE, TRUE, 0); + + gtk_widget_grab_focus(entry); + } + } + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, FALSE, FALSE, 0); + + { + auto button = create_modal_dialog_button("OK", ok_button); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Return, (GdkModifierType) 0, + GTK_ACCEL_VISIBLE); + } + { + auto button = create_modal_dialog_button("Cancel", cancel_button); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType) 0, + GTK_ACCEL_VISIBLE); + } + } + } + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + if (ret == eIDOK) { + *tag = gtk_entry_get_text(textentry); + } + + window.destroy(); + + return ret; +} + +EMessageBoxReturn DoShaderInfoDlg(const char *name, const char *filename, const char *title) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + + auto window = MainFrame_getWindow().create_modal_dialog_window(title, dialog, -1, -1); + + auto accel_group = ui::AccelGroup(ui::New); + window.add_accel_group(accel_group); + + { + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, FALSE, FALSE, 0); + { + auto label = ui::Label("The selected shader"); + label.show(); + vbox.pack_start(label, FALSE, FALSE, 0); + } + { + auto label = ui::Label(name); + label.show(); + vbox.pack_start(label, FALSE, FALSE, 0); + } + { + auto label = ui::Label("is located in file"); + label.show(); + vbox.pack_start(label, FALSE, FALSE, 0); + } + { + auto label = ui::Label(filename); + label.show(); + vbox.pack_start(label, FALSE, FALSE, 0); + } + { + auto button = create_modal_dialog_button("OK", ok_button); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Return, (GdkModifierType) 0, + GTK_ACCEL_VISIBLE); + } + } + } + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + + window.destroy(); + + return ret; +} + + +#if GDEF_OS_WINDOWS +#include +#endif + +#if GDEF_OS_WINDOWS +// 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) +{ +#if GDEF_OS_WINDOWS + if ( g_TextEditor_useWin32Editor ) { + globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n"; + ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "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) == 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 0000000..fa8ac88 --- /dev/null +++ b/radiant/gtkdlgs.h @@ -0,0 +1,68 @@ +/* + 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 "globaldefs.h" +#include "qerplugin.h" +#include "string/string.h" + +EMessageBoxReturn DoLightIntensityDlg(int *intensity); + +EMessageBoxReturn DoShaderTagDlg(CopiedString *tag, const char *title); + +EMessageBoxReturn DoShaderInfoDlg(const char *name, const char *filename, const 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(); + + +#if GDEF_OS_WINDOWS +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 0000000..e4e784c --- /dev/null +++ b/radiant/gtkmisc.cpp @@ -0,0 +1,163 @@ +/* + 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 +#include "gtkmisc.h" + +#include "uilib/uilib.h" + +#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, 1); + 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, 2); + global_accel_group_connect(toggle.m_command.m_accelerator, toggle.m_command.m_callback); +} + +void toggle_remove_accelerator(const char *name) +{ + const Toggle &toggle = GlobalToggles_find(name); + global_accel_group_disconnect(toggle.m_command.m_accelerator, toggle.m_command.m_callback); +} + +ui::CheckMenuItem create_check_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const char *commandName) +{ + GlobalShortcuts_register(commandName, 2); + 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); +} + +ui::MenuItem create_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const char *commandName) +{ + GlobalShortcuts_register(commandName, 1); + 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); +} + +ui::ToolButton +toolbar_append_button(ui::Toolbar toolbar, const char *description, const char *icon, const char *commandName) +{ + return toolbar_append_button(toolbar, description, icon, GlobalCommands_find(commandName)); +} + +ui::ToggleToolButton +toolbar_append_toggle_button(ui::Toolbar 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(ui::Window parent, Vector3 &color, const char *title) +{ + GdkColor clr = {0, guint16(color[0] * 65535), guint16(color[1] * 65535), guint16(color[2] * 65535)}; + ModalDialog dialog; + + auto dlg = ui::Window::from(gtk_color_selection_dialog_new(title)); + gtk_color_selection_set_current_color( + GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(dlg))), &clr); + dlg.connect("delete_event", G_CALLBACK(dialog_delete_callback), &dialog); + GtkWidget *ok_button, *cancel_button; + g_object_get(G_OBJECT(dlg), "ok-button", &ok_button, "cancel-button", &cancel_button, nullptr); + ui::Widget::from(ok_button).connect("clicked", G_CALLBACK(dialog_button_ok), &dialog); + ui::Widget::from(cancel_button).connect("clicked", G_CALLBACK(dialog_button_cancel), &dialog); + + if (parent) { + gtk_window_set_transient_for(dlg, parent); + } + + bool ok = modal_dialog_show(dlg, dialog) == eIDOK; + if (ok) { + gtk_color_selection_get_current_color( + GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(dlg))), + &clr); + color[0] = clr.red / 65535.0f; + color[1] = clr.green / 65535.0f; + color[2] = clr.blue / 65535.0f; + } + + dlg.destroy(); + + return ok; +} + +void button_clicked_entry_browse_file(ui::Widget widget, ui::Entry entry) +{ + const char *filename = widget.file_dialog(TRUE, "Choose File", gtk_entry_get_text(entry)); + + if (filename != 0) { + entry.text(filename); + } +} + +void button_clicked_entry_browse_directory(ui::Widget widget, ui::Entry entry) +{ + const char *text = gtk_entry_get_text(entry); + char *dir = dir_dialog(widget.window(), "Choose Directory", path_is_absolute(text) ? text : ""); + + if (dir != 0) { + gchar *converted = g_filename_to_utf8(dir, -1, 0, 0, 0); + entry.text(converted); + g_free(dir); + g_free(converted); + } +} diff --git a/radiant/gtkmisc.h b/radiant/gtkmisc.h new file mode 100644 index 0000000..0379797 --- /dev/null +++ b/radiant/gtkmisc.h @@ -0,0 +1,71 @@ +/* + 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 + +void command_connect_accelerator(const char *commandName); + +void command_disconnect_accelerator(const char *commandName); + +void toggle_add_accelerator(const char *commandName); + +void toggle_remove_accelerator(const char *name); + +// this also sets up the shortcut using command_connect_accelerator +ui::MenuItem create_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const char *commandName); + +// this also sets up the shortcut using command_connect_accelerator +ui::CheckMenuItem create_check_menu_item_with_mnemonic(ui::Menu menu, const char *mnemonic, const char *commandName); + + +// this DOES NOT set up the shortcut using command_connect_accelerator +ui::ToolButton +toolbar_append_button(ui::Toolbar toolbar, const char *description, const char *icon, const char *commandName); + +// this DOES NOT set up the shortcut using command_connect_accelerator +ui::ToggleToolButton +toolbar_append_toggle_button(ui::Toolbar toolbar, const char *description, const char *icon, const char *commandName); + + +template +class BasicVector3; + +typedef BasicVector3 Vector3; + +bool color_dialog(ui::Window parent, Vector3 &color, const char *title = "Choose Color"); + +void button_clicked_entry_browse_file(ui::Widget widget, ui::Entry entry); + +void button_clicked_entry_browse_directory(ui::Widget widget, ui::Entry entry); + +#endif diff --git a/radiant/help.cpp b/radiant/help.cpp new file mode 100644 index 0000000..baf04de --- /dev/null +++ b/radiant/help.cpp @@ -0,0 +1,132 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#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, ui::Menu menu) +{ + if (file_exists(filename)) { + xmlDocPtr pDoc = xmlParseFile(filename); + if (pDoc) { + globalOutputStream() << "Processing .xlink file '" << filename << "'\n"; + // create sub menu + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, menu_name); + /*if (g_Layout_enableOpenStepUX.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://") || + strstr(reinterpret_cast( prop ), "https://")) { + // 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(ui::Menu 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 0000000..e16cb8b --- /dev/null +++ b/radiant/help.h @@ -0,0 +1,29 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#if !defined( INCLUDED_HELP_H ) +#define INCLUDED_HELP_H + +void create_game_help_menu(ui::Menu menu); + +#endif diff --git a/radiant/image.cpp b/radiant/image.cpp new file mode 100644 index 0000000..71f365a --- /dev/null +++ b/radiant/image.cpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 0000000..b05323b --- /dev/null +++ b/radiant/image.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_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 0000000..2ba14f5 --- /dev/null +++ b/radiant/main.cpp @@ -0,0 +1,701 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 MemberCaller0 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 "globaldefs.h" + +#include "version.h" + +#include "debugging/debugging.h" + +#include "iundo.h" + +#include "uilib/uilib.h" + +#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" + +#if GDEF_OS_WINDOWS +#include +#endif + +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"); + } + + // spam it... + globalErrorStream() << buf << "\n"; + + if (is_fatal) { + ERROR_MESSAGE("GTK+ error: " << buf); + } +} + +#if GDEF_COMPILER_MSVC && GDEF_DEBUG +#include "crtdbg.h" +#endif + +void crt_init() +{ +#if GDEF_COMPILER_MSVC && GDEF_DEBUG + _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 (GDEF_DEBUG) { + m_buffer << "Break into the debugger?\n"; + bool handled = ui::alert(ui::root, m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::YESNO, + ui::alert_icon::Error) == ui::alert_response::NO; + m_buffer.clear(); + return handled; + } else { + m_buffer << "Please report this error to the developers\n"; + ui::alert(ui::root, m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::OK, + ui::alert_icon::Error); + m_buffer.clear(); + } + } + return true; + } +}; + +typedef Static GlobalPopupDebugMessageHandler; + +void streams_init() +{ + GlobalErrorStream::instance().setOutputStream(getSysPrintErrorStream()); + GlobalOutputStream::instance().setOutputStream(getSysPrintOutputStream()); +} + +void paths_init() +{ + g_strSettingsPath = environment_get_home_path(); + + 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 + if (GDEF_DEBUG) { + return true; + } + // locate and open WorldSpawn_MAJOR and WorldSpawn_MINOR + bool bVerIsGood = true; + { + StringOutputStream ver_file_name(256); + ver_file_name << AppPath_get() << "WorldSpawn_MAJOR"; + bVerIsGood = check_version_file(ver_file_name.c_str(), WorldSpawn_MAJOR_VERSION); + } + { + StringOutputStream ver_file_name(256); + ver_file_name << AppPath_get() << "WorldSpawn_MINOR"; + bVerIsGood = check_version_file(ver_file_name.c_str(), WorldSpawn_MINOR_VERSION); + } + + if (!bVerIsGood) { + StringOutputStream msg(256); + msg + << "This editor binary (" WorldSpawn_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(); + ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Default); + } + return bVerIsGood; +} + +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(); + ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error); + } + + // in debug, never prompt to clean registry, turn console logging auto after a failed start + if (!GDEF_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 (ui::alert(ui::root, msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, + ui::alert_icon::Question) == ui::alert_response::YES) { + g_GamesDialog.Reset(); + } + + msg.clear(); + msg << "Logging console output to " << SettingsPath_get() + << "radiant.log\nRefer to the log if Radiant fails to start again."; + + ui::alert(ui::root, msg.c_str(), "Radiant - Console Log", ui::alert_type::OK); + } + + // 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(); + ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error); + } +} + +/*! + 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(); + ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error); + } + + // in debug, never prompt to clean registry, turn console logging auto after a failed start + if (!GDEF_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 (ui::alert(ui::root, msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, + ui::alert_icon::Question) == ui::alert_response::YES) { + Preferences_Reset(); + } + + msg.clear(); + msg << "Logging console output to " << SettingsPath_get() + << "radiant.log\nRefer to the log if Radiant fails to start again."; + + ui::alert(ui::root, msg.c_str(), "Radiant - Console Log", ui::alert_type::OK); + } + + // 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()); +} + +void user_shortcuts_save() +{ + StringOutputStream path(256); + path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/'; + SaveCommandMap(path.c_str()); +} + +int main(int argc, char *argv[]) +{ + crt_init(); + + streams_init(); + +#if GDEF_OS_WINDOWS + HMODULE lib; + lib = LoadLibrary( "dwmapi.dll" ); + if ( lib != 0 ) { + void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" ); + if ( qDwmEnableComposition ) { + qDwmEnableComposition( FALSE ); + } + FreeLibrary( lib ); + } +#endif + + const char *mapname = NULL; + char const *error = NULL; + if (!ui::init(&argc, &argv, "", &error)) { + g_print("%s\n", error); + return -1; + } + + // Gtk already removed parsed `--options` + if (argc == 2) { + if (strlen(argv[1]) > 1) { + if (g_str_has_suffix(argv[1], ".map")) { + if (g_path_is_absolute(argv[1])) { + mapname = argv[1]; + } else { + mapname = g_build_filename(g_get_current_dir(), argv[1], NULL); + } + } else { + g_print("bad file name, will not load: %s\n", argv[1]); + } + } + } else if (argc > 2) { + g_print("%s\n", "too many arguments"); + return -1; + } + + // 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 | + G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), 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 | + G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), 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 | + G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), 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 | + G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), 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 | + G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), error_redirect, 0); + + GlobalDebugMessageHandler::instance().setHandler(GlobalPopupDebugMessageHandler::instance()); + + environment_init(argc, (char const **) argv); + + paths_init(); + + if (!check_version()) { + return EXIT_FAILURE; + } + + + + create_global_pid(); + + GlobalPreferences_Init(); + + g_GamesDialog.Init(); + + if (g_GamesDialog.m_bGamePrompt == false) { + show_splash(); + } + + 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(); + + user_shortcuts_init(); + + g_pParentWnd = 0; + g_pParentWnd = new MainFrame(); + + hide_splash(); + + if (mapname != NULL) { + Map_LoadFile(mapname); + } else 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(); + + ui::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; + + user_shortcuts_save(); + + 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 0000000..d87b7a8 --- /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 0000000..ec9a164 --- /dev/null +++ b/radiant/mainframe.cpp @@ -0,0 +1,3587 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 "globaldefs.h" + +#include + +#include "ifilesystem.h" +#include "iundo.h" +#include "editable.h" +#include "ientity.h" +#include "ishaders.h" +#include "igl.h" +#include "moduleobserver.h" + +#include + +#include + + +#include "cmdlib.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/frame.h" +#include "gtkutil/glwidget.h" +#include "gtkutil/image.h" +#include "gtkutil/menu.h" +#include "gtkutil/paned.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 "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" +#include "texwindow.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 + +bool g_vfsInitialized = false; + +void VFS_Init() +{ + if (g_vfsInitialized) { return; } + QE_InitVFS(); + GlobalFileSystem().initialise(); + g_vfsInitialized = true; +} + +void VFS_Shutdown() +{ + if (!g_vfsInitialized) { return; } + GlobalFileSystem().shutdown(); + g_vfsInitialized = false; +} + +void VFS_Refresh() +{ + if (!g_vfsInitialized) { return; } + GlobalFileSystem().clear(); + QE_InitVFS(); + GlobalFileSystem().refresh(); + g_vfsInitialized = true; + // also refresh models + RefreshReferences(); + // also refresh texture browser + TextureBrowser_RefreshShaders(); +} + +void VFS_Restart() +{ + VFS_Shutdown(); + VFS_Init(); +} + +class VFSModuleObserver : public ModuleObserver { +public: + void realise() + { + VFS_Init(); + } + + void unrealise() + { + VFS_Shutdown(); + } +}; + +VFSModuleObserver g_VFSModuleObserver; + +void VFS_Construct() +{ + Radiant_attachHomePathsObserver(g_VFSModuleObserver); +} + +void VFS_Destroy() +{ + Radiant_detachHomePathsObserver(g_VFSModuleObserver); +} + +// Home Paths + +#if GDEF_OS_WINDOWS +#include +#include +const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}}; +#define qREFKNOWNFOLDERID GUID +#define qKF_FLAG_CREATE 0x8000 +#define qKF_FLAG_NO_ALIAS 0x1000 +typedef HRESULT ( WINAPI qSHGetKnownFolderPath_t )( qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath ); +static qSHGetKnownFolderPath_t *qSHGetKnownFolderPath; +#endif + +void HomePaths_Realise() +{ + do { + const char *prefix = g_pGameDescription->getKeyValue("prefix"); + if (!string_empty(prefix)) { + StringOutputStream path(256); + +#if GDEF_OS_MACOS + path.clear(); + path << DirectoryCleaned( g_get_home_dir() ) << "Library/Application Support" << ( prefix + 1 ) << "/"; + if ( file_is_directory( path.c_str() ) ) { + g_qeglobals.m_userEnginePath = path.c_str(); + break; + } + path.clear(); + path << DirectoryCleaned( g_get_home_dir() ) << prefix << "/"; +#endif + +#if GDEF_OS_WINDOWS + TCHAR mydocsdir[MAX_PATH + 1]; + wchar_t *mydocsdirw; + HMODULE shfolder = LoadLibrary( "shfolder.dll" ); + if ( shfolder ) { + qSHGetKnownFolderPath = (qSHGetKnownFolderPath_t *) GetProcAddress( shfolder, "SHGetKnownFolderPath" ); + } + else{ + qSHGetKnownFolderPath = NULL; + } + CoInitializeEx( NULL, COINIT_APARTMENTTHREADED ); + if ( qSHGetKnownFolderPath && qSHGetKnownFolderPath( qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &mydocsdirw ) == S_OK ) { + memset( mydocsdir, 0, sizeof( mydocsdir ) ); + wcstombs( mydocsdir, mydocsdirw, sizeof( mydocsdir ) - 1 ); + CoTaskMemFree( mydocsdirw ); + path.clear(); + path << DirectoryCleaned( mydocsdir ) << ( prefix + 1 ) << "/"; + if ( file_is_directory( path.c_str() ) ) { + g_qeglobals.m_userEnginePath = path.c_str(); + CoUninitialize(); + FreeLibrary( shfolder ); + break; + } + } + CoUninitialize(); + if ( shfolder ) { + FreeLibrary( shfolder ); + } + if ( SHGetFolderPath( NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir ) ) { + path.clear(); + path << DirectoryCleaned( mydocsdir ) << "My Games/" << ( prefix + 1 ) << "/"; + // win32: only add it if it already exists + if ( file_is_directory( path.c_str() ) ) { + g_qeglobals.m_userEnginePath = path.c_str(); + break; + } + } +#endif + +#if GDEF_OS_POSIX + path.clear(); + path << DirectoryCleaned(g_get_home_dir()) << prefix << "/"; + g_qeglobals.m_userEnginePath = path.c_str(); + break; +#endif + } + + g_qeglobals.m_userEnginePath = EnginePath_get(); + } while (0); + + Q_mkdir(g_qeglobals.m_userEnginePath.c_str()); + + { + 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(); + } +} + +// Pak Path + +CopiedString g_strPakPath[g_pakPathCount] = {"", "", "", "", ""}; +ModuleObservers g_pakPathObservers[g_pakPathCount]; +std::size_t g_pakpath_unrealised[g_pakPathCount] = {1, 1, 1, 1, 1}; + +void Radiant_attachPakPathObserver(int num, ModuleObserver &observer) +{ + g_pakPathObservers[num].attach(observer); +} + +void Radiant_detachPakPathObserver(int num, ModuleObserver &observer) +{ + g_pakPathObservers[num].detach(observer); +} + + +void PakPath_Realise(int num) +{ + if (--g_pakpath_unrealised[num] == 0) { + g_pakPathObservers[num].realise(); + } +} + +const char *PakPath_get(int num) +{ + std::string message = "PakPath_get: pak path " + std::to_string(num) + " not realised"; + ASSERT_MESSAGE(g_pakpath_unrealised[num] == 0, message.c_str()); + return g_strPakPath[num].c_str(); +} + +void PakPath_Unrealise(int num) +{ + if (++g_pakpath_unrealised[num] == 1) { + g_pakPathObservers[num].unrealise(); + } +} + +void setPakPath(int num, const char *path) +{ + if (!g_strcmp0(path, "")) { + g_strPakPath[num] = ""; + return; + } + + StringOutputStream buffer(256); + buffer << DirectoryCleaned(path); + if (!path_equal(buffer.c_str(), g_strPakPath[num].c_str())) { + std::string message = "Changing Pak Path " + std::to_string(num); + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", message.c_str()); + + PakPath_Unrealise(num); + + g_strPakPath[num] = buffer.c_str(); + + PakPath_Realise(num); + } +} + + +// 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(); +} + +struct EnginePath { + static void Export(const CopiedString &self, const Callback &returnz) + { + returnz(self.c_str()); + } + + static void Import(CopiedString &self, const char *value) + { + setEnginePath(value); + } +}; + +struct PakPath0 { + static void Export(const CopiedString &self, const Callback &returnz) + { + returnz(self.c_str()); + } + + static void Import(CopiedString &self, const char *value) + { + setPakPath(0, value); + } +}; + +struct PakPath1 { + static void Export(const CopiedString &self, const Callback &returnz) + { + returnz(self.c_str()); + } + + static void Import(CopiedString &self, const char *value) + { + setPakPath(1, value); + } +}; + +struct PakPath2 { + static void Export(const CopiedString &self, const Callback &returnz) + { + returnz(self.c_str()); + } + + static void Import(CopiedString &self, const char *value) + { + setPakPath(2, value); + } +}; + +struct PakPath3 { + static void Export(const CopiedString &self, const Callback &returnz) + { + returnz(self.c_str()); + } + + static void Import(CopiedString &self, const char *value) + { + setPakPath(3, value); + } +}; + +struct PakPath4 { + static void Export(const CopiedString &self, const Callback &returnz) + { + returnz(self.c_str()); + } + + static void Import(CopiedString &self, const char *value) + { + setPakPath(4, value); + } +}; + +bool g_disableEnginePath = false; +bool g_disableHomePath = false; + +void Paths_constructPreferences(PreferencesPage &page) +{ + page.appendPathEntry("Engine Path", true, make_property(g_strEnginePath)); + + page.appendCheckBox( + "", "Do not use Engine Path", + g_disableEnginePath + ); + + page.appendCheckBox( + "", "Do not use Home Path", + g_disableHomePath + ); + + for (int i = 0; i < g_pakPathCount; i++) { + std::string label = "Pak Path " + std::to_string(i); + switch (i) { + case 0: + page.appendPathEntry(label.c_str(), true, make_property(g_strPakPath[i])); + break; + case 1: + page.appendPathEntry(label.c_str(), true, make_property(g_strPakPath[i])); + break; + case 2: + page.appendPathEntry(label.c_str(), true, make_property(g_strPakPath[i])); + break; + case 3: + page.appendPathEntry(label.c_str(), true, make_property(g_strPakPath[i])); + break; + case 4: + page.appendPathEntry(label.c_str(), true, make_property(g_strPakPath[i])); + break; + } + } +} + +void Paths_constructPage(PreferenceGroup &group) +{ + PreferencesPage page(group.createPage("Paths", "Path Settings")); + Paths_constructPreferences(page); +} + +void Paths_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(makeCallbackF(Paths_constructPage)); +} + + +class PathsDialog : public Dialog { +public: + ui::Window BuildDialog() + { + auto frame = create_dialog_frame("Path settings", ui::Shadow::ETCHED_IN); + + auto vbox2 = create_dialog_vbox(0, 4); + frame.add(vbox2); + + { + PreferencesPage preferencesPage(*this, vbox2); + Paths_constructPreferences(preferencesPage); + } + + return ui::Window(create_simple_modal_dialog_window("Engine Path Not Found", m_modal, 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" + +const char *const c_library_extension = +#if defined( CMAKE_SHARED_MODULE_SUFFIX ) + CMAKE_SHARED_MODULE_SUFFIX +#elif GDEF_OS_WINDOWS + "dll" +#elif GDEF_OS_MACOS + "dylib" +#elif GDEF_OS_LINUX || GDEF_OS_BSD + "so" +#endif +; + +void Radiant_loadModules(const char *path) +{ + Directory_forEach(path, matchFileExtension(c_library_extension, [&](const char *name) { + char fullname[1024]; + ASSERT_MESSAGE(strlen(path) + strlen(name) < 1024, ""); + strcpy(fullname, path); + strcat(fullname, name); + globalOutputStream() << "Found '" << fullname << "'\n"; + GlobalModuleServer_loadModule(fullname); + })); +} + +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 WorldSpawn")) { + 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), GetSnapGridSize()), mid); + + // Move to camera + GlobalSelectionSystem().translateSelected(delta); +} + + +void ColorScheme_WorldSpawn() +{ + TextureBrowser_setBackgroundColour(GlobalTextureBrowser(), Vector3(0.15f, 0.15f, 0.15f)); + + g_camwindow_globals.color_cameraback = Vector3(0.15f, 0.15f, 0.15f); + 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.45f, 0.45f, 0.45f); + g_xywindow_globals.color_gridblock = Vector3(0.39f, 0.18f, 0.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(0.55f, 0.55f, 0.55f); + SetWorldspawnColour(g_xywindow_globals.color_brushes); + g_xywindow_globals.color_viewname = Vector3(0.7f, 0.7f, 0.0f); + XY_UpdateAllWindows(); +} + +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 Callback GetColourCallback; +typedef Callback 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(MainFrame_getWindow(), colour); + m_set(colour); + } +}; + + +void Colour_get(const Vector3 &colour, Vector3 &other) +{ + other = colour; +} + +typedef ConstReferenceCaller ColourGetCaller; + +void Colour_set(Vector3 &colour, const Vector3 &other) +{ + colour = other; + SceneChangeNotify(); +} + +typedef ReferenceCaller ColourSetCaller; + +void BrushColour_set(const Vector3 &other) +{ + g_xywindow_globals.color_brushes = other; + SetWorldspawnColour(g_xywindow_globals.color_brushes); + SceneChangeNotify(); +} + +typedef FreeCaller BrushColourSetCaller; + +void ClipperColour_set(const Vector3 &other) +{ + g_xywindow_globals.color_clipper = other; + Brush_clipperColourChanged(); + SceneChangeNotify(); +} + +typedef FreeCaller ClipperColourSetCaller; + +void TextureBrowserColour_get(Vector3 &other) +{ + other = TextureBrowser_getBackgroundColour(GlobalTextureBrowser()); +} + +typedef FreeCaller TextureBrowserColourGetCaller; + +void TextureBrowserColour_set(const Vector3 &other) +{ + TextureBrowser_setBackgroundColour(GlobalTextureBrowser(), other); +} + +typedef FreeCaller 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; + +ui::MenuItem create_colours_menu() +{ + auto colours_menu_item = new_sub_menu_item_with_mnemonic("Colors"); + auto menu_in_menu = ui::Menu::from(gtk_menu_item_get_submenu(colours_menu_item)); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu_in_menu); + }*/ + + auto menu_3 = create_sub_menu_with_mnemonic(menu_in_menu, "Themes"); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu_3); + }*/ + + create_menu_item_with_mnemonic(menu_3, "Default", "ColorSchemeWS"); + 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 OpenHelpURL() +{ + OpenURL("https://www.vera-visions.com/developer/"); +} + +void OpenBugReportURL() +{ + OpenURL("https://www.vera-visions.com/developer/"); +} + + +ui::Widget g_page_console{ui::null}; + +void Console_ToggleShow() +{ + GroupDialog_showPage(g_page_console); +} + +ui::Widget g_page_entity{ui::null}; + +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 Callback &importCallback) + { + importCallback(BoolFunction()); + } +}; + +typedef FreeCaller &), &BoolFunctionExport::apply> EdgeModeApplyCaller; +EdgeModeApplyCaller g_edgeMode_button_caller; +Callback &)> g_edgeMode_button_callback(g_edgeMode_button_caller); +ToggleItem g_edgeMode_button(g_edgeMode_button_callback); + +typedef FreeCaller &), &BoolFunctionExport::apply> VertexModeApplyCaller; +VertexModeApplyCaller g_vertexMode_button_caller; +Callback &)> g_vertexMode_button_callback(g_vertexMode_button_caller); +ToggleItem g_vertexMode_button(g_vertexMode_button_callback); + +typedef FreeCaller &), &BoolFunctionExport::apply> FaceModeApplyCaller; +FaceModeApplyCaller g_faceMode_button_caller; +Callback &)> 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 { + bool doMakeUnique; + NodeSmartReference worldspawn; +public: + CloneSelected(bool d) : doMakeUnique(d), worldspawn(Map_FindOrInsertWorldspawn(g_map)) + { + } + + bool pre(const scene::Path &path, scene::Instance &instance) const + { + if (path.size() == 1) { + return true; + } + + // ignore worldspawn, but keep checking children + NodeSmartReference me(path.top().get()); + if (me == worldspawn) { + 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; + } + + // ignore worldspawn, but keep checking children + NodeSmartReference me(path.top().get()); + if (me == worldspawn) { + return; + } + + if (!path.top().get().isRoot()) { + Selectable *selectable = Instance_getSelectable(instance); + if (selectable != 0 + && selectable->isSelected()) { + NodeSmartReference clone(Node_Clone(path.top())); + if (doMakeUnique) { + Map_gatherNamespaced(clone); + } + Node_getTraversable(path.parent().get())->insert(clone); + } + } + } +}; + +void Scene_Clone_Selected(scene::Graph &graph, bool doMakeUnique) +{ + graph.traverse(CloneSelected(doMakeUnique)); + + 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(), false); + + //NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType()); + //NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType()); + } +} + +void Selection_Clone_MakeUnique() +{ + if (GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) { + UndoableCommand undo("cloneSelectedMakeUnique"); + + Scene_Clone_Selected(GlobalSceneGraph(), true); + + //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 Callback &importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eTranslate); +} + +void RotateToolExport(const Callback &importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eRotate); +} + +void ScaleToolExport(const Callback &importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eScale); +} + +void DragToolExport(const Callback &importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eDrag); +} + +void ClipperToolExport(const Callback &importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip); +} + +FreeCaller &), TranslateToolExport> g_translatemode_button_caller; +Callback &)> g_translatemode_button_callback(g_translatemode_button_caller); +ToggleItem g_translatemode_button(g_translatemode_button_callback); + +FreeCaller &), RotateToolExport> g_rotatemode_button_caller; +Callback &)> g_rotatemode_button_callback(g_rotatemode_button_caller); +ToggleItem g_rotatemode_button(g_rotatemode_button_callback); + +FreeCaller &), ScaleToolExport> g_scalemode_button_caller; +Callback &)> g_scalemode_button_callback(g_scalemode_button_caller); +ToggleItem g_scalemode_button(g_scalemode_button_callback); + +FreeCaller &), DragToolExport> g_dragmode_button_caller; +Callback &)> g_dragmode_button_callback(g_dragmode_button_caller); +ToggleItem g_dragmode_button(g_dragmode_button_callback); + +FreeCaller &), ClipperToolExport> g_clipper_button_caller; +Callback &)> 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 = "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 = g_timeout_add(1000, qe_every_second, 0); + } +} + +void EverySecondTimer_disable() +{ + if (s_qe_every_second_id != 0) { + g_source_remove(s_qe_every_second_id); + s_qe_every_second_id = 0; + } +} + +gint window_realize_remove_decoration(ui::Widget widget, gpointer data) +{ + gdk_window_set_decorations(gtk_widget_get_window(widget), + (GdkWMDecoration) (GDK_DECOR_ALL | GDK_DECOR_MENU | GDK_DECOR_MINIMIZE | + GDK_DECOR_MAXIMIZE)); + return FALSE; +} + +class WaitDialog { +public: + ui::Window m_window{ui::null}; + ui::Label m_label{ui::null}; +}; + +WaitDialog create_wait_dialog(const char *title, const char *text) +{ + WaitDialog dialog; + + dialog.m_window = MainFrame_getWindow().create_floating_window(title); + 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); + + dialog.m_window.connect("realize", G_CALLBACK(window_realize_remove_decoration), 0); + + { + dialog.m_label = ui::Label(text); + gtk_misc_set_alignment(GTK_MISC(dialog.m_label), 0.0, 0.5); + gtk_label_set_justify(dialog.m_label, GTK_JUSTIFY_LEFT); + dialog.m_label.show(); + dialog.m_label.dimensions(200, -1); + + dialog.m_window.add(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(ui::Window::from(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() && g_wait.m_window.visible()) { + ui::process(); + } +} + + +void ScreenUpdates_Disable(const char *message, const char *title) +{ + if (g_wait_stack.empty()) { + EverySecondTimer_disable(); + + ui::process(); + + bool isActiveApp = MainFrame_isActiveApp(); + + g_wait = create_wait_dialog(title, message); + gtk_grab_add(g_wait.m_window); + + if (isActiveApp) { + g_wait.m_window.show(); + ScreenUpdates_process(); + } + } else if (g_wait.m_window.visible()) { + g_wait.m_label.text(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(MainFrame_getWindow(), TRUE); + + gtk_grab_remove(g_wait.m_window); + destroy_floating_window(g_wait.m_window); + g_wait.m_window = ui::Window{ui::null}; + + //gtk_window_present(MainFrame_getWindow()); + } else if (g_wait.m_window.visible()) { + g_wait.m_label.text(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(); +} + +LatchedValue g_Layout_enablePluginToolbar(true, "Plugin Toolbar"); + + +ui::MenuItem create_file_menu() +{ + // File menu + auto file_menu_item = new_sub_menu_item_with_mnemonic("_File"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(file_menu_item)); + /*if (g_Layout_enableOpenStepUX.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, "_Export selected...", "ExportSelected"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Save re_gion...", "SaveRegion"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "_Refresh assets", "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, "E_xit", "Exit"); + + return file_menu_item; +} + +ui::MenuItem create_edit_menu() +{ + // Edit menu + auto edit_menu_item = new_sub_menu_item_with_mnemonic("_Edit"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(edit_menu_item)); + /*if (g_Layout_enableOpenStepUX.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, "Duplicate, make uni_que", "CloneSelectionAndMakeUnique"); + 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"); + + create_check_menu_item_with_mnemonic(menu, "Auto-Expand Selection", "ToggleExpansion"); + + auto convert_menu = create_sub_menu_with_mnemonic(menu, "E_xpand Selection"); + /*if (g_Layout_enableOpenStepUX.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(ui::Menu menu) +{ + create_check_menu_item_with_mnemonic(menu, "XY (Top) View", "ToggleView"); +} + + +void fill_view_yz_side_menu(ui::Menu menu) +{ + create_check_menu_item_with_mnemonic(menu, "YZ (Side) View", "ToggleSideView"); +} + + +void fill_view_xz_front_menu(ui::Menu menu) +{ + create_check_menu_item_with_mnemonic(menu, "XZ (Front) View", "ToggleFrontView"); +} + + +ui::Widget g_toggle_z_item{ui::null}; +ui::Widget g_toggle_console_item{ui::null}; +ui::Widget g_toggle_entity_item{ui::null}; +ui::Widget g_toggle_entitylist_item{ui::null}; + +ui::MenuItem create_view_menu() +{ + // View menu + auto view_menu_item = new_sub_menu_item_with_mnemonic("Vie_w"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(view_menu_item)); + + + /*fill_view_camera_menu(menu); + fill_view_xy_top_menu(menu); + fill_view_yz_side_menu(menu); + fill_view_xz_front_menu(menu);*/ + + create_menu_item_with_mnemonic(menu, "Map Info", "MapInfo"); + 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"); + + + create_menu_item_with_mnemonic(menu, "_Surface Inspector", "SurfaceInspector"); + create_menu_item_with_mnemonic(menu, "Entity List", "EntityList"); + + menu_separator(menu); + menu.add(create_colours_menu()); + { + auto camera_menu = create_sub_menu_with_mnemonic(menu, "Camera"); + + 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"); + } + + { + auto orthographic_menu = create_sub_menu_with_mnemonic(menu, "Orthographic"); + + 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); + + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Show"); + /*if (g_Layout_enableOpenStepUX.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 Lighting", "ShowLighting"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show Alpha", "ShowAlpha"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show Stats", "ShowStats"); + } + + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Filter"); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu_in_menu); + }*/ + Filters_constructMenu(menu_in_menu); + } + menu_separator(menu); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Hide/Show"); + /*if (g_Layout_enableOpenStepUX.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, "Hide Unselected", "HideUnselected"); + create_menu_item_with_mnemonic(menu_in_menu, "Show Hidden", "ShowHidden"); + } + menu_separator(menu); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Region"); + /*if (g_Layout_enableOpenStepUX.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"); + } + + command_connect_accelerator("CenterXYView"); + + return view_menu_item; +} + +ui::MenuItem create_selection_menu() +{ + // Selection menu + auto selection_menu_item = new_sub_menu_item_with_mnemonic("M_odify"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(selection_menu_item)); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu); + }*/ + + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Components"); + /*if (g_Layout_enableOpenStepUX.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); + + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Nudge"); + /*if (g_Layout_enableOpenStepUX.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"); + } + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Rotate"); + /*if (g_Layout_enableOpenStepUX.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"); + } + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Flip"); + /*if (g_Layout_enableOpenStepUX.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"); + create_menu_item_with_mnemonic(menu, "Find brush...", "FindBrush"); + return selection_menu_item; +} + +ui::MenuItem create_bsp_menu() +{ + // BSP menu + auto bsp_menu_item = new_sub_menu_item_with_mnemonic("_Build"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(bsp_menu_item)); + + /*if (g_Layout_enableOpenStepUX.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; +} + +ui::MenuItem create_grid_menu() +{ + // Grid menu + auto grid_menu_item = new_sub_menu_item_with_mnemonic("_Grid"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(grid_menu_item)); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu); + }*/ + + Grid_constructMenu(menu); + + return grid_menu_item; +} + +ui::MenuItem create_entity_menu() +{ + // Brush menu + auto entity_menu_item = new_sub_menu_item_with_mnemonic("E_ntity"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(entity_menu_item)); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu); + }*/ + + Entity_constructMenu(menu); + + return entity_menu_item; +} + +ui::MenuItem create_brush_menu() +{ + // Brush menu + auto brush_menu_item = new_sub_menu_item_with_mnemonic("B_rush"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(brush_menu_item)); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu); + }*/ + + Brush_constructMenu(menu); + + return brush_menu_item; +} + +ui::MenuItem create_patch_menu() +{ + // Curve menu + auto patch_menu_item = new_sub_menu_item_with_mnemonic("_Curve"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(patch_menu_item)); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu); + }*/ + + Patch_constructMenu(menu); + + return patch_menu_item; +} + +ui::MenuItem create_help_menu() +{ + // Help menu + auto help_menu_item = new_sub_menu_item_with_mnemonic("_Help"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(help_menu_item)); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu); + }*/ + + // 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, "Shortcuts list", makeCallbackF(DoCommandListDlg)); + create_menu_item_with_mnemonic(menu, "_About", makeCallbackF(DoAbout)); + + return help_menu_item; +} + +ui::MenuBar create_main_menu() +{ + auto menu_bar = ui::MenuBar::from(gtk_menu_bar_new()); + menu_bar.show(); + menu_bar.add(create_file_menu()); + menu_bar.add(create_edit_menu()); + menu_bar.add(create_view_menu()); + menu_bar.add(create_selection_menu()); + menu_bar.add(create_bsp_menu()); + menu_bar.add(create_grid_menu()); + menu_bar.add(create_entity_menu()); + menu_bar.add(create_brush_menu()); + menu_bar.add(create_patch_menu()); + menu_bar.add(create_plugins_menu()); + menu_bar.add(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("PatchInsertInsertColumn"); + command_connect_accelerator("PatchInsertInsertRow"); + command_connect_accelerator("PatchDeleteLastColumn"); + command_connect_accelerator("PatchDeleteLastRow"); + 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(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "New map", "file_new.xpm", "NewMap"); + toolbar_append_button(toolbar, "Open an existing map (CTRL + O)", "file_open.xpm", "OpenMap"); + toolbar_append_button(toolbar, "Save the active map (CTRL + S)", "file_save.xpm", "SaveMap"); +} + +void UndoRedo_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "Undo (CTRL + Z)", "undo.xpm", "Undo"); + toolbar_append_button(toolbar, "Redo (CTRL + Y)", "redo.xpm", "Redo"); +} + +void Rotate_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "x-axis Rotate", "brush_rotatex.xpm", "RotateSelectionX"); + toolbar_append_button(toolbar, "y-axis Rotate", "brush_rotatey.xpm", "RotateSelectionZ"); + toolbar_append_button(toolbar, "z-axis Rotate", "brush_rotatez.xpm", "RotateSelectionY"); +} + +void Flip_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "x-axis Flip", "brush_flipx.xpm", "MirrorSelectionX"); + toolbar_append_button(toolbar, "y-axis Flip", "brush_flipy.xpm", "MirrorSelectionY"); + toolbar_append_button(toolbar, "z-axis Flip", "brush_flipz.xpm", "MirrorSelectionZ"); +} + +void Select_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "Select touching", "selection_selecttouching.xpm", "SelectTouching"); + toolbar_append_button(toolbar, "Select inside", "selection_selectinside.xpm", "SelectInside"); +} + +void CSG_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "CSG Subtract (SHIFT + U)", "selection_csgsubtract.xpm", "CSGSubtract"); + toolbar_append_button(toolbar, "CSG Merge (CTRL + U)", "selection_csgmerge.xpm", "CSGMerge"); + toolbar_append_button(toolbar, "Make Hollow", "selection_makehollow.xpm", "CSGMakeHollow"); + toolbar_append_button(toolbar, "Make Room", "selection_makeroom.xpm", "CSGMakeRoom"); +} + +void ComponentModes_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_toggle_button(toolbar, "Select Vertices (V)", "modify_vertices.xpm", "DragVertices"); + toolbar_append_toggle_button(toolbar, "Select Edges (E)", "modify_edges.xpm", "DragEdges"); + toolbar_append_toggle_button(toolbar, "Select Faces (F)", "modify_faces.xpm", "DragFaces"); +} + +void Clipper_constructToolbar(ui::Toolbar toolbar) +{ + + toolbar_append_toggle_button(toolbar, "Clipper (X)", "view_clipper.xpm", "ToggleClipper"); +} + +void XYWnd_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "Change views", "view_change.xpm", "NextView"); +} + +void Manipulators_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_toggle_button(toolbar, "Translate (W)", "select_mousetranslate.xpm", "MouseTranslate"); + toolbar_append_toggle_button(toolbar, "Rotate (R)", "select_mouserotate.xpm", "MouseRotate"); + toolbar_append_toggle_button(toolbar, "Scale", "select_mousescale.xpm", "MouseScale"); + toolbar_append_toggle_button(toolbar, "Resize (Q)", "select_mouseresize.xpm", "MouseDrag"); + + Clipper_constructToolbar(toolbar); +} + +void PluginToolbar_AddToMain(ui::Toolbar toolbar); +ui::Toolbar create_main_toolbar() +{ + auto toolbar = ui::Toolbar::from(gtk_toolbar_new()); + gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL); + gtk_toolbar_set_style(toolbar, GTK_TOOLBAR_ICONS); + + toolbar.show(); + + auto space = [&]() { + auto btn = ui::ToolItem::from(gtk_separator_tool_item_new()); + btn.show(); + toolbar.add(btn); + }; + + File_constructToolbar(toolbar); + space(); + UndoRedo_constructToolbar(toolbar); + space(); + Rotate_constructToolbar(toolbar); + space(); + Flip_constructToolbar(toolbar); + space(); + Select_constructToolbar(toolbar); + space(); + CSG_constructToolbar(toolbar); + + + space(); + XYWnd_constructToolbar(toolbar); + + + space(); + CamWnd_constructToolbar(toolbar); + + space(); + Patch_constructToolbar(toolbar); + + space(); + toolbar_append_toggle_button(toolbar, "Texture Lock (SHIFT +T)", "texture_lock.xpm", "TogTexLock"); + space(); + toolbar_append_button(toolbar, "Console (O)", "console.xpm", "ToggleConsole"); + space(); + toolbar_append_button(toolbar, "Refresh Assets", "refresh_models.xpm", + "RefreshReferences"); + + PluginToolbar_AddToMain(toolbar); + + + return toolbar; +} + +ui::Widget create_main_statusbar(ui::Widget pStatusLabel[c_count_status]) +{ + auto table = ui::Table(1, c_count_status, FALSE); + table.show(); + + { + auto label = ui::Label("Label"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_misc_set_padding(GTK_MISC(label), 4, 2); + label.show(); + table.attach(label, {0, 1, 0, 1}); + pStatusLabel[c_command_status] = ui::Widget(label); + } + + for (unsigned int i = 1; (int) i < c_count_status; ++i) { + auto frame = ui::Frame(); + frame.show(); + table.attach(frame, {i, i + 1, 0, 1}); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + + auto label = ui::Label("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); + label.show(); + frame.add(label); + pStatusLabel[i] = ui::Widget(label); + } + + return ui::Widget(table); +} + + +ui::Toolbar create_main_sidebar() +{ + auto toolbar = ui::Toolbar::from(gtk_toolbar_new()); + gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_VERTICAL); + gtk_toolbar_set_style(toolbar, GTK_TOOLBAR_ICONS); + + toolbar.show(); + + auto space = [&]() { + auto btn = ui::ToolItem::from(gtk_separator_tool_item_new()); + btn.show(); + toolbar.add(btn); + }; + + toolbar_append_toggle_button(toolbar, "Select Vertices (V)", "side_vertices.xpm", "DragVertices"); + toolbar_append_toggle_button(toolbar, "Select Edges (E)", "side_edges.xpm", "DragEdges"); + toolbar_append_toggle_button(toolbar, "Select Faces (F)", "side_faces.xpm", "DragFaces"); + space(); + toolbar_append_toggle_button(toolbar, "Drag (Q)", "side_resize.xpm", "MouseDrag"); + toolbar_append_toggle_button(toolbar, "Translate (W)", "side_transform.xpm", "MouseTranslate"); + toolbar_append_toggle_button(toolbar, "Rotate (R)", "side_rotate.xpm", "MouseRotate"); + toolbar_append_toggle_button(toolbar, "Scale", "side_scale.xpm", "MouseScale"); + toolbar_append_toggle_button(toolbar, "Clipper (X)", "side_clipper.xpm", "ToggleClipper"); + space(); + toolbar_append_button(toolbar, "Entity Inspector (N)", "side_entities.xpm", "ToggleEntityInspector"); + toolbar_append_button(toolbar, "Texture Browser (T)", "side_textures.xpm", "ToggleTextures"); + toolbar_append_button(toolbar, "Surface Inspector (S)", "side_surface.xpm", "SurfaceInspector"); + toolbar_append_button(toolbar, "Patch Inspector (SHIFT + S)", "side_patch.xpm", "PatchInspector"); + space(); + toolbar_append_button(toolbar, "Find Brush", "side_find.xpm", "FindBrush"); + + return toolbar; +} + +#if 0 + + +WidgetFocusPrinter g_mainframeWidgetFocusPrinter( "mainframe" ); + +class WindowFocusPrinter +{ +const char* m_name; + +static gboolean frame_event( ui::Widget widget, GdkEvent* event, WindowFocusPrinter* self ){ + globalOutputStream() << self->m_name << " frame_event\n"; + return FALSE; +} +static gboolean keys_changed( ui::Widget widget, WindowFocusPrinter* self ){ + globalOutputStream() << self->m_name << " keys_changed\n"; + return FALSE; +} +static gboolean notify( ui::Window 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( ui::Window toplevel_window ){ + toplevel_window.connect( "notify::has_toplevel_focus", G_CALLBACK( notify ), this ); + toplevel_window.connect( "notify::is_active", G_CALLBACK( notify ), this ); + toplevel_window.connect( "keys_changed", G_CALLBACK( keys_changed ), this ); + toplevel_window.connect( "frame_event", G_CALLBACK( frame_event ), this ); +} +}; + +WindowFocusPrinter g_mainframeFocusPrinter( "mainframe" ); + +#endif + +class MainWindowActive { + static gboolean notify(ui::Window window, gpointer dummy, MainWindowActive *self) + { + if (g_wait.m_window && gtk_window_is_active(window) && !g_wait.m_window.visible()) { + g_wait.m_window.show(); + } + + return FALSE; + } + +public: + void connect(ui::Window toplevel_window) + { + toplevel_window.connect("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; + +ui::Window MainFrame_getWindow() +{ + return g_pParentWnd ? g_pParentWnd->m_window : ui::Window{ui::null}; +} + +std::vector g_floating_windows; + +MainFrame::MainFrame() : m_idleRedrawStatusText(RedrawStatusTextCaller(*this)) +{ + m_pXYWnd = 0; + m_pCamWnd = 0; + m_pZWnd = 0; + m_pYZWnd = 0; + m_pXZWnd = 0; + m_pActiveXY = 0; + + for (auto &n : m_pStatusLabel) { + n = NULL; + } + + m_bSleeping = false; + + Create(); +} + +MainFrame::~MainFrame() +{ + SaveWindowInfo(); + + m_window.hide(); + + Shutdown(); + + for (std::vector::iterator i = g_floating_windows.begin(); i != g_floating_windows.end(); ++i) { + i->destroy(); + } + + m_window.destroy(); +} + +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 +} + +#if GDEF_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 +} + + +ui::Window create_splash() +{ + auto window = ui::Window(ui::window_type::TOP); + 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(window, 0); + + auto image = new_local_image("splash.xpm"); + image.show(); + window.add(image); + + window.dimensions(-1, -1); + window.show(); + + return window; +} + +static ui::Window splash_screen{ui::null}; + +void show_splash() +{ + splash_screen = create_splash(); + + ui::process(); +} + +void hide_splash() +{ + splash_screen.destroy(); +} + +WindowPositionTracker g_posCamWnd; +WindowPositionTracker g_posXYWnd; +WindowPositionTracker g_posXZWnd; +WindowPositionTracker g_posYZWnd; + +static gint mainframe_delete(ui::Widget widget, GdkEvent *event, gpointer data) +{ + if (ConfirmModified("Exit WorldSpawn")) { + gtk_main_quit(); + } + + return TRUE; +} + +void MainFrame::Create() +{ + ui::Window window = ui::Window(ui::window_type::TOP); + GlobalWindowObservers_connectTopLevel(window); + gtk_window_set_transient_for(splash_screen, window); + +#if !GDEF_OS_WINDOWS + { + GdkPixbuf *pixbuf = pixbuf_new_from_file_with_mask("bitmaps/icon.xpm"); + if (pixbuf != 0) { + gtk_window_set_icon(window, pixbuf); + g_object_unref(pixbuf); + } + } +#endif + + gtk_widget_add_events(window, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK); + window.connect("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(window); + + auto vbox = ui::VBox(FALSE, 0); + auto hbox = ui::HBox(FALSE, 0); + window.add(vbox); + window.add(hbox); + vbox.show(); + hbox.show(); + + global_accel_connect_window(window); + + register_shortcuts(); + + auto main_menu = create_main_menu(); + vbox.pack_start(main_menu, FALSE, FALSE, 0); + + auto main_toolbar = create_main_toolbar(); + vbox.pack_start(main_toolbar, FALSE, FALSE, 0); + + /*if (!g_Layout_enablePluginToolbar.m_value) { + plugin_toolbar.hide(); + }*/ + + ui::Widget main_statusbar = create_main_statusbar(reinterpret_cast(m_pStatusLabel)); + vbox.pack_end(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")); + /*} +*/ + m_window = window; + + window.show(); + + m_pCamWnd = NewCamWnd(); + GlobalCamera_setCamWnd(*m_pCamWnd); + CamWnd_setParent(*m_pCamWnd, window); + + ui::Widget camera = CamWnd_getWidget(*m_pCamWnd); + + m_pYZWnd = new XYWnd(); + m_pYZWnd->SetViewType(YZ); + + ui::Widget yz = m_pYZWnd->GetWidget(); + + m_pXYWnd = new XYWnd(); + m_pXYWnd->SetViewType(XY); + + ui::Widget xy = m_pXYWnd->GetWidget(); + + m_pXZWnd = new XYWnd(); + m_pXZWnd->SetViewType(XZ); + + ui::Widget xz = m_pXZWnd->GetWidget(); + + auto split = create_split_views(camera, yz, xy, xz); + vbox.pack_start(hbox, TRUE, TRUE, 0); + + auto main_sidebar = create_main_sidebar(); + hbox.pack_start(main_sidebar, FALSE, FALSE, 2); + + hbox.pack_start(split, TRUE, TRUE, 0); + + { + auto frame = create_framed_widget(TextureBrowser_constructWindow(window)); + g_page_textures = GroupDialog_addPage("Textures", frame, TextureBrowserExportTitleCaller()); + } + + #if GDEF_OS_WINDOWS + if ( g_multimon_globals.m_bStartOnPrimMon ) { + PositionWindowOnPrimaryScreen( g_layout_globals.m_position ); + window_set_position( window, g_layout_globals.m_position ); + } + #endif + + if (g_layout_globals.nState & GDK_WINDOW_STATE_MAXIMIZED) { + WindowPosition default_position(-1, -1, 640, 480); + window_set_position(window, default_position); + gtk_window_maximize(window); + } else { + window_set_position(window, g_layout_globals.m_position); + gtk_window_resize(window, g_layout_globals.m_position.w, g_layout_globals.m_position.h); + } + + 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(); +} + +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_get_window(m_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() +{ + ui::Label::from(m_pStatusLabel[c_command_status]).text(m_command_status.c_str()); + ui::Label::from(m_pStatusLabel[c_position_status]).text(m_position_status.c_str()); + ui::Label::from(m_pStatusLabel[c_brushcount_status]).text(m_brushcount_status.c_str()); + ui::Label::from(m_pStatusLabel[c_texture_status]).text(m_texture_status.c_str()); + ui::Label::from(m_pStatusLabel[c_grid_status]).text(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 << (GetSnapGridSize() > 0 ? "G:" : "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(); + } +} + +void GlobalGL_sharedContextCreated() +{ + GLFont *g_font = NULL; + + // 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"; + const auto extensions = reinterpret_cast( glGetString(GL_EXTENSIONS)); + globalOutputStream() << "GL_EXTENSIONS: " << (extensions ? extensions : "") << "\n"; + + QGL_sharedContextCreated(GlobalOpenGL()); + + ShaderCache_extensionsInitialised(); + + GlobalShaderCache().realise(); + Textures_Realise(); + + g_font = glfont_create("Misc Fixed 12"); + + GlobalOpenGL().m_font = g_font; +} + +void GlobalGL_sharedContextDestroyed() +{ + Textures_Unrealise(); + GlobalShaderCache().unrealise(); + + QGL_sharedContextDestroyed(GlobalOpenGL()); +} + + +void Layout_constructPreferences(PreferencesPage &page) +{ + + page.appendCheckBox( + "", "Plugin Toolbar", + make_property(g_Layout_enablePluginToolbar) + ); +} + +void Layout_constructPage(PreferenceGroup &group) +{ + PreferencesPage page(group.createPage("Layout", "Layout Preferences")); + Layout_constructPreferences(page); +} + +void Layout_registerPreferencesPage() +{ + PreferencesDialog_addInterfacePage(makeCallbackF(Layout_constructPage)); +} + +/* eukara: this makes things more sane */ +bool g_expansion_enabled = true; +Callback g_expansion_status_changed; +ConstReferenceCaller &), PropertyImpl::Export> g_expansion_caller( + g_expansion_enabled); +ToggleItem g_expansion_item(g_expansion_caller); + +void Texdef_ToggleExpansion() +{ + g_expansion_enabled = !g_expansion_enabled; + g_expansion_item.update(); + g_expansion_status_changed(); +} + +#include "preferencesystem.h" +#include "stringio.h" + +void MainFrame_Construct() +{ + /*GlobalCommands_insert("Sleep", makeCallbackF(thunk_OnSleep), + Accelerator('P', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));*/ + GlobalCommands_insert("NewMap", makeCallbackF(NewMap)); + GlobalCommands_insert("OpenMap", makeCallbackF(OpenMap), Accelerator('O', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("ImportMap", makeCallbackF(ImportMap)); + GlobalCommands_insert("SaveMap", makeCallbackF(SaveMap), Accelerator('S', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("SaveMapAs", makeCallbackF(SaveMapAs)); + GlobalCommands_insert("ExportSelected", makeCallbackF(ExportMap)); + GlobalCommands_insert("SaveRegion", makeCallbackF(SaveRegion)); + GlobalCommands_insert("RefreshReferences", makeCallbackF(VFS_Refresh)); + GlobalCommands_insert("ProjectSettings", makeCallbackF(DoProjectSettings)); + GlobalCommands_insert("Exit", makeCallbackF(Exit)); + + GlobalCommands_insert("Undo", makeCallbackF(Undo), Accelerator('Z', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("Redo", makeCallbackF(Redo), Accelerator('Y', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("Copy", makeCallbackF(Copy), Accelerator('C', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("Paste", makeCallbackF(Paste), Accelerator('V', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("PasteToCamera", makeCallbackF(PasteToCamera), + Accelerator('V', (GdkModifierType) GDK_MOD1_MASK)); + GlobalCommands_insert("CloneSelection", makeCallbackF(Selection_Clone), Accelerator(GDK_KEY_space)); + GlobalCommands_insert("CloneSelectionAndMakeUnique", makeCallbackF(Selection_Clone_MakeUnique), + Accelerator(GDK_KEY_space, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("DeleteSelection", makeCallbackF(deleteSelection), Accelerator(GDK_KEY_BackSpace)); + GlobalCommands_insert("ParentSelection", makeCallbackF(Scene_parentSelected)); + GlobalCommands_insert("UnSelectSelection", makeCallbackF(Selection_Deselect), Accelerator(GDK_KEY_Escape)); + GlobalCommands_insert("InvertSelection", makeCallbackF(Select_Invert), Accelerator('I')); + GlobalCommands_insert("SelectInside", makeCallbackF(Select_Inside)); + GlobalCommands_insert("SelectTouching", makeCallbackF(Select_Touching)); + GlobalCommands_insert("ExpandSelectionToEntities", makeCallbackF(Scene_ExpandSelectionToEntities), + Accelerator('E', (GdkModifierType) (GDK_MOD1_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("Preferences", makeCallbackF(PreferencesDialog_showDialog)); + + GlobalCommands_insert("ToggleConsole", makeCallbackF(Console_ToggleShow), Accelerator('O')); + GlobalCommands_insert("ToggleEntityInspector", makeCallbackF(EntityInspector_ToggleShow), Accelerator('N')); + GlobalCommands_insert("EntityList", makeCallbackF(EntityList_toggleShown), Accelerator('L')); + + GlobalCommands_insert("ShowHidden", makeCallbackF(Select_ShowAllHidden), + Accelerator('H', (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("HideSelected", makeCallbackF(HideSelected), Accelerator('H')); + GlobalCommands_insert("HideUnselected", makeCallbackF(HideUnselected)); + + GlobalToggles_insert("DragVertices", makeCallbackF(SelectVertexMode), + ToggleItem::AddCallbackCaller(g_vertexMode_button), Accelerator('V')); + GlobalToggles_insert("DragEdges", makeCallbackF(SelectEdgeMode), ToggleItem::AddCallbackCaller(g_edgeMode_button), + Accelerator('E')); + GlobalToggles_insert("DragFaces", makeCallbackF(SelectFaceMode), ToggleItem::AddCallbackCaller(g_faceMode_button), + Accelerator('F')); + + GlobalCommands_insert("MirrorSelectionX", makeCallbackF(Selection_Flipx)); + GlobalCommands_insert("RotateSelectionX", makeCallbackF(Selection_Rotatex)); + GlobalCommands_insert("MirrorSelectionY", makeCallbackF(Selection_Flipy)); + GlobalCommands_insert("RotateSelectionY", makeCallbackF(Selection_Rotatey)); + GlobalCommands_insert("MirrorSelectionZ", makeCallbackF(Selection_Flipz)); + GlobalCommands_insert("RotateSelectionZ", makeCallbackF(Selection_Rotatez)); + + GlobalCommands_insert("ArbitraryRotation", makeCallbackF(DoRotateDlg)); + GlobalCommands_insert("ArbitraryScale", makeCallbackF(DoScaleDlg)); + + GlobalCommands_insert("BuildMenuCustomize", makeCallbackF(DoBuildMenu)); + + GlobalCommands_insert("FindBrush", makeCallbackF(DoFind)); + + GlobalCommands_insert("MapInfo", makeCallbackF(DoMapInfo), Accelerator('M')); + + GlobalToggles_insert("ToggleExpansion", makeCallbackF(Texdef_ToggleExpansion), + ToggleItem::AddCallbackCaller(g_expansion_item)); + + GlobalToggles_insert("ToggleClipper", makeCallbackF(ClipperMode), ToggleItem::AddCallbackCaller(g_clipper_button), + Accelerator('X')); + + GlobalToggles_insert("MouseTranslate", makeCallbackF(TranslateMode), + ToggleItem::AddCallbackCaller(g_translatemode_button), Accelerator('W')); + GlobalToggles_insert("MouseRotate", makeCallbackF(RotateMode), ToggleItem::AddCallbackCaller(g_rotatemode_button), + Accelerator('R')); + GlobalToggles_insert("MouseScale", makeCallbackF(ScaleMode), ToggleItem::AddCallbackCaller(g_scalemode_button)); + GlobalToggles_insert("MouseDrag", makeCallbackF(DragMode), ToggleItem::AddCallbackCaller(g_dragmode_button), + Accelerator('Q')); + + GlobalCommands_insert("ColorSchemeWS", makeCallbackF(ColorScheme_WorldSpawn)); + GlobalCommands_insert("ColorSchemeOriginal", makeCallbackF(ColorScheme_Original)); + GlobalCommands_insert("ColorSchemeQER", makeCallbackF(ColorScheme_QER)); + GlobalCommands_insert("ColorSchemeBlackAndGreen", makeCallbackF(ColorScheme_Black)); + GlobalCommands_insert("ColorSchemeYdnar", makeCallbackF(ColorScheme_Ydnar)); + 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", makeCallbackF(CSG_Subtract), + Accelerator('U', (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("CSGMerge", makeCallbackF(CSG_Merge), Accelerator('U', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("CSGMakeHollow", makeCallbackF(CSG_MakeHollow)); + GlobalCommands_insert("CSGMakeRoom", makeCallbackF(CSG_MakeRoom)); + + Grid_registerCommands(); + + GlobalCommands_insert("SnapToGrid", makeCallbackF(Selection_SnapToGrid), + Accelerator('G', (GdkModifierType) GDK_CONTROL_MASK)); + + GlobalCommands_insert("SelectAllOfType", makeCallbackF(Select_AllOfType), + Accelerator('A', (GdkModifierType) GDK_SHIFT_MASK)); + + GlobalCommands_insert("TexRotateClock", makeCallbackF(Texdef_RotateClockwise), + Accelerator(GDK_KEY_Next, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("TexRotateCounter", makeCallbackF(Texdef_RotateAntiClockwise), + Accelerator(GDK_KEY_Prior, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("TexScaleUp", makeCallbackF(Texdef_ScaleUp), + Accelerator(GDK_KEY_Up, (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("TexScaleDown", makeCallbackF(Texdef_ScaleDown), + Accelerator(GDK_KEY_Down, (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("TexScaleLeft", makeCallbackF(Texdef_ScaleLeft), + Accelerator(GDK_KEY_Left, (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("TexScaleRight", makeCallbackF(Texdef_ScaleRight), + Accelerator(GDK_KEY_Right, (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("TexShiftUp", makeCallbackF(Texdef_ShiftUp), + Accelerator(GDK_KEY_Up, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("TexShiftDown", makeCallbackF(Texdef_ShiftDown), + Accelerator(GDK_KEY_Down, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("TexShiftLeft", makeCallbackF(Texdef_ShiftLeft), + Accelerator(GDK_KEY_Left, (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("TexShiftRight", makeCallbackF(Texdef_ShiftRight), + Accelerator(GDK_KEY_Right, (GdkModifierType) GDK_SHIFT_MASK)); + + GlobalCommands_insert("MoveSelectionDOWN", makeCallbackF(Selection_MoveDown), Accelerator(GDK_KEY_KP_Subtract)); + GlobalCommands_insert("MoveSelectionUP", makeCallbackF(Selection_MoveUp), Accelerator(GDK_KEY_KP_Add)); + + GlobalCommands_insert("SelectNudgeLeft", makeCallbackF(Selection_NudgeLeft), + Accelerator(GDK_KEY_Left, (GdkModifierType) GDK_MOD1_MASK)); + GlobalCommands_insert("SelectNudgeRight", makeCallbackF(Selection_NudgeRight), + Accelerator(GDK_KEY_Right, (GdkModifierType) GDK_MOD1_MASK)); + GlobalCommands_insert("SelectNudgeUp", makeCallbackF(Selection_NudgeUp), + Accelerator(GDK_KEY_Up, (GdkModifierType) GDK_MOD1_MASK)); + GlobalCommands_insert("SelectNudgeDown", makeCallbackF(Selection_NudgeDown), + Accelerator(GDK_KEY_Down, (GdkModifierType) GDK_MOD1_MASK)); + + Patch_registerCommands(); + XYShow_registerCommands(); + + typedef FreeCaller ComponentModeSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(ComponentModeSelectionChangedCaller()); + + GlobalPreferenceSystem().registerPreference("PluginToolBar", + make_property_string(g_Layout_enablePluginToolbar.m_latched)); + GlobalPreferenceSystem().registerPreference("XYHeight", make_property_string(g_layout_globals.nXYHeight)); + GlobalPreferenceSystem().registerPreference("XYWidth", make_property_string(g_layout_globals.nXYWidth)); + GlobalPreferenceSystem().registerPreference("CamWidth", make_property_string(g_layout_globals.nCamWidth)); + GlobalPreferenceSystem().registerPreference("CamHeight", make_property_string(g_layout_globals.nCamHeight)); + + GlobalPreferenceSystem().registerPreference("State", make_property_string(g_layout_globals.nState)); + GlobalPreferenceSystem().registerPreference("PositionX", make_property_string(g_layout_globals.m_position.x)); + GlobalPreferenceSystem().registerPreference("PositionY", make_property_string(g_layout_globals.m_position.y)); + GlobalPreferenceSystem().registerPreference("Width", make_property_string(g_layout_globals.m_position.w)); + GlobalPreferenceSystem().registerPreference("Height", make_property_string(g_layout_globals.m_position.h)); + + GlobalPreferenceSystem().registerPreference("CamWnd", make_property(g_posCamWnd)); + GlobalPreferenceSystem().registerPreference("XYWnd", make_property(g_posXYWnd)); + GlobalPreferenceSystem().registerPreference("YZWnd", make_property(g_posYZWnd)); + GlobalPreferenceSystem().registerPreference("XZWnd", make_property(g_posXZWnd)); + + { + const char *ENGINEPATH_ATTRIBUTE = +#if GDEF_OS_WINDOWS + "enginepath_win32" +#elif GDEF_OS_MACOS + "enginepath_macos" +#elif GDEF_OS_LINUX || GDEF_OS_BSD + "enginepath_linux" +#else +#error "unknown platform" +#endif + ; + StringOutputStream path(256); + path << DirectoryCleaned(g_pGameDescription->getRequiredKeyValue(ENGINEPATH_ATTRIBUTE)); + g_strEnginePath = path.c_str(); + } + + GlobalPreferenceSystem().registerPreference("EnginePath", make_property_string(g_strEnginePath)); + + GlobalPreferenceSystem().registerPreference("DisableEnginePath", make_property_string(g_disableEnginePath)); + GlobalPreferenceSystem().registerPreference("DisableHomePath", make_property_string(g_disableHomePath)); + + for (int i = 0; i < g_pakPathCount; i++) { + std::string label = "PakPath" + std::to_string(i); + GlobalPreferenceSystem().registerPreference(label.c_str(), make_property_string(g_strPakPath[i])); + } + + g_Layout_enablePluginToolbar.useLatched(); + + Layout_registerPreferencesPage(); + Paths_registerPreferencesPage(); + + g_brushCount.setCountChangedCallback(makeCallbackF(QE_brushCountChanged)); + g_entityCount.setCountChangedCallback(makeCallbackF(QE_entityCountChanged)); + 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", make_property_string(g_glwindow_globals.m_nMouseType)); +} + +void GLWindow_Destroy() +{ +} diff --git a/radiant/mainframe.h b/radiant/mainframe.h new file mode 100644 index 0000000..a8c81dd --- /dev/null +++ b/radiant/mainframe.h @@ -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 + */ + +#if !defined( INCLUDED_MAINFRAME_H ) +#define INCLUDED_MAINFRAME_H + +#include +#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; + + +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(); + + ui::Window m_window{ui::null}; + + 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(); + + ui::Widget m_vSplit{ui::null}; + ui::Widget m_hSplit{ui::null}; + ui::Widget m_vSplit2{ui::null}; + + XYWnd *m_pXYWnd; + XYWnd *m_pYZWnd; + XYWnd *m_pXZWnd; + CamWnd *m_pCamWnd; + ZWnd *m_pZWnd; + XYWnd *m_pActiveXY; + + bool m_bSleeping; + + void *m_pStatusLabel[c_count_status]; + + 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(); +}; + +extern MainFrame *g_pParentWnd; + +ui::Window 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; + +extern LatchedValue g_Layout_enableOpenStepUX; + +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 bool g_disableEnginePath; +extern bool g_disableHomePath; + +const int g_pakPathCount = 5; +extern CopiedString g_strPakPath[g_pakPathCount]; + +const char *PakPath_get(int num); + +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_Refresh(); + +void VFS_Restart(); + +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); + +extern ui::Widget g_page_entity; + +#endif diff --git a/radiant/map.cpp b/radiant/map.cpp new file mode 100644 index 0000000..f94aad3 --- /dev/null +++ b/radiant/map.cpp @@ -0,0 +1,2380 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 + +#include "debugging/debugging.h" + +#include "imap.h" + +MapModules &ReferenceAPI_getMapModules(); + +#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 "iarchive.h" +#include "ifilesystem.h" +#include "namespace.h" +#include "moduleobserver.h" + +#include + +#include +#include "uilib/uilib.h" + +#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 "preferencesystem.h" +#include "referencecache.h" +#include "mru.h" +#include "commands.h" +#include "autosave.h" +#include "brushmodule.h" +#include "brush.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 MemberCaller 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; + Brush_unlatchPreferences(); +} + +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; + ui::Entry brushes_entry{ui::null}; + ui::Entry entities_entry{ui::null}; + ui::ListStore EntityBreakdownWalker{ui::null}; + + ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback), + &dialog); + + window_set_position(window, g_posMapInfoWnd); + + { + auto vbox = create_dialog_vbox(4, 4); + window.add(vbox); + + { + auto hbox = create_dialog_hbox(4); + vbox.pack_start(hbox, FALSE, TRUE, 0); + + { + auto table = create_dialog_table(2, 2, 4, 4); + hbox.pack_start(table, TRUE, TRUE, 0); + + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE); + + brushes_entry = entry; + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE); + + entities_entry = entry; + } + { + ui::Widget label = ui::Label("Total Brushes"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + ui::Widget label = ui::Label("Total Entities"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + } + { + auto vbox2 = create_dialog_vbox(4); + hbox.pack_start(vbox2, FALSE, FALSE, 0); + + { + auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog); + vbox2.pack_start(button, FALSE, FALSE, 0); + } + } + } + { + ui::Widget label = ui::Label("Entity breakdown"); + label.show(); + vbox.pack_start(label, FALSE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4); + vbox.pack_start(scr, TRUE, TRUE, 0); + + { + auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING)); + + auto view = ui::TreeView(ui::TreeModel::from(store._handle)); + gtk_tree_view_set_headers_clickable(view, TRUE); + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("Entity", renderer, {{"text", 0}}); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_sort_column_id(column, 0); + } + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("Count", renderer, {{"text", 1}}); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_sort_column_id(column, 1); + } + + view.show(); + + scr.add(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)); + EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp); + } + } + + EntityBreakdownWalker.unref(); + + char tmp[16]; + sprintf(tmp, "%u", Unsigned(g_brushCount.get())); + brushes_entry.text(tmp); + sprintf(tmp, "%u", Unsigned(g_entityCount.get())); + entities_entry.text(tmp); + + modal_dialog_show(window, dialog); + + // save before exit + window_get_position(window, g_posMapInfoWnd); + + window.destroy(); +} + + +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"; + } +}; + +CopiedString g_strLastFolder = ""; + +/* + ================ + Map_LoadFile + ================ + */ + +void Map_LoadFile(const char *filename) +{ + globalOutputStream() << "Loading map from " << filename << "\n"; + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map"); + + MRU_AddFile(filename); + g_strLastFolder = g_path_get_dirname(filename); + + { + ScopeTimer timer("map load"); + + const MapFormat *format = NULL; + const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename)); + if (string_not_empty(moduleName)) { + format = ReferenceAPI_getMapModules().findModule(moduleName); + } + + for (int i = 0; i < Brush_toggleFormatCount(); ++i) { + if (i) { + Map_Free(); + } + Brush_toggleFormat(i); + g_map.m_name = filename; + Map_UpdateTitle(g_map); + g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str()); + if (format) { + format->wrongFormat = false; + } + g_map.m_resource->attach(g_map); + if (format) { + if (!format->wrongFormat) { + break; + } + } + } + + Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn()); + } + + globalOutputStream() << "--- LoadMapFile ---\n"; + globalOutputStream() << g_map.m_name.c_str() << "\n"; + + globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n"; + globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n"; + + //GlobalEntityCreator().printStatistics(); + + // + // move the view to a start position + // + Map_StartPosition(); + + g_currentMap = &g_map; + + // refresh VFS to apply new pak filtering based on mapname + // needed for daemon DPK VFS + VFS_Refresh(); +} + +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); + // refresh VFS to apply new pak filtering based on mapname + // needed for daemon DPK VFS + VFS_Refresh(); +} + +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; + + // restart VFS to apply new pak filtering based on mapname + // needed for daemon DPK VFS + VFS_Restart(); +} + +extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs); + +void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 ®ion_mins, const Vector3 ®ion_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"); + + g_strLastFolder = g_path_get_dirname(filename); + + bool success = false; + + if (extension_equal(path_get_extension(filename), "bsp")) { + goto tryDecompile; + } + + { + const MapFormat *format = NULL; + const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename)); + if (string_not_empty(moduleName)) { + format = ReferenceAPI_getMapModules().findModule(moduleName); + } + + if (format) { + format->wrongFormat = false; + } + Resource *resource = GlobalReferenceCache().capture(filename); + resource->refresh(); // avoid loading old version if map has changed on disk since last import + if (!resource->load()) { + GlobalReferenceCache().release(filename); + goto tryDecompile; + } + if (format) { + if (format->wrongFormat) { + GlobalReferenceCache().release(filename); + goto tryDecompile; + } + } + NodeSmartReference clone(NewMapRoot("")); + Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone)); + Map_gatherNamespaced(clone); + Map_mergeClonedNames(); + MergeMap(clone); + success = true; + GlobalReferenceCache().release(filename); + } + + SceneChangeNotify(); + + return success; + + tryDecompile: + + const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type"); + int n = string_length(path_get_extension(filename)); + if (n && (extension_equal(path_get_extension(filename), "bsp") || + extension_equal(path_get_extension(filename), "map"))) { + StringBuffer output; + output.push_string(AppPath_get()); + output.push_string("q3map2."); + output.push_string(WorldSpawn_EXECUTABLE); + output.push_string(" -v -game "); + output.push_string((type && *type) ? type : "quake3"); + output.push_string(" -fs_basepath \""); + output.push_string(EnginePath_get()); + output.push_string("\" -fs_homepath \""); + output.push_string(g_qeglobals.m_userEnginePath.c_str()); + output.push_string("\""); + + // extra pakpaths + for (int i = 0; i < g_pakPathCount; i++) { + if (g_strcmp0(g_strPakPath[i].c_str(), "")) { + output.push_string(" -fs_pakpath \""); + output.push_string(g_strPakPath[i].c_str()); + output.push_string("\""); + } + } + + // extra switches + if (g_disableEnginePath) { + output.push_string(" -fs_nobasepath "); + } + + if (g_disableHomePath) { + output.push_string(" -fs_nohomepath "); + } + + output.push_string(" -fs_game "); + output.push_string(gamename_get()); + output.push_string(" -convert -format "); + output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map"); + if (extension_equal(path_get_extension(filename), "map")) { + output.push_string(" -readmap "); + } + output.push_string(" \""); + output.push_string(filename); + output.push_string("\""); + + // run + Q_Exec(NULL, output.c_str(), NULL, false, true); + + // rebuild filename as "filenamewithoutext_converted.map" + output.clear(); + output.push_range(filename, filename + string_length(filename) - (n + 1)); + output.push_string("_converted.map"); + filename = output.c_str(); + + // open + Resource *resource = GlobalReferenceCache().capture(filename); + resource->refresh(); // avoid loading old version if map has changed on disk since last import + if (!resource->load()) { + GlobalReferenceCache().release(filename); + goto tryDecompile; + } + NodeSmartReference clone(NewMapRoot("")); + 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"); + bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename); + if (success) { + // refresh VFS to apply new pak filtering based on mapname + // needed for daemon DPK VFS + VFS_Refresh(); + } + return success; +} + +// +//=========== +//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 *getLastFolderPath() +{ + if (g_strLastFolder.empty()) { + GlobalPreferenceSystem().registerPreference("LastFolder", make_property_string(g_strLastFolder)); + if (g_strLastFolder.empty()) { + g_strLastFolder = g_qeglobals.m_userGamePath; + } + } + return g_strLastFolder.c_str(); +} + +const char *map_open(const char *title) +{ + return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false); +} + +const char *map_import(const char *title) +{ + return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false); +} + +const char *map_save(const char *title) +{ + return MainFrame_getWindow().file_dialog(FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true); +} + +void OpenMap() +{ + if (!ConfirmModified("Open Map")) { + return; + } + + const char *filename = map_open("Open Map"); + + if (filename != NULL) { + MRU_AddFile(filename); + Map_RegionOff(); + Map_Free(); + Map_LoadFile(filename); + } +} + +void ImportMap() +{ + const char *filename = map_import("Import Map"); + + if (filename != NULL) { + UndoableCommand undo("mapImport"); + Map_ImportFile(filename); + } +} + +bool Map_SaveAs() +{ + const char *filename = map_save("Save Map"); + + if (filename != NULL) { + g_strLastFolder = g_path_get_dirname(filename); + 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 != NULL) { + g_strLastFolder = g_path_get_dirname(filename); + Map_SaveSelected(filename); + } +} + +void SaveRegion() +{ + const char *filename = map_save("Export Region"); + + if (filename != NULL) { + g_strLastFolder = g_path_get_dirname(filename); + 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; + ui::Entry entity{ui::null}; + ui::Entry brush{ui::null}; + + ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback), + &dialog); + + auto accel = ui::AccelGroup(ui::New); + window.add_accel_group(accel); + + { + auto vbox = create_dialog_vbox(4, 4); + window.add(vbox); + { + auto table = create_dialog_table(2, 2, 4, 4); + vbox.pack_start(table, TRUE, TRUE, 0); + { + ui::Widget label = ui::Label("Entity number"); + label.show(); + (table).attach(label, {0, 1, 0, 1}, {0, 0}); + } + { + ui::Widget label = ui::Label("Brush number"); + label.show(); + (table).attach(label, {0, 1, 1, 2}, {0, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + gtk_widget_grab_focus(entry); + entity = entry; + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + + brush = entry; + } + } + { + auto hbox = create_dialog_hbox(4); + vbox.pack_start(hbox, TRUE, TRUE, 0); + { + auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog); + hbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog); + hbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + } + } + + // Initialize dialog + char buf[16]; + int ent, br; + + GetSelectionIndex(&ent, &br); + sprintf(buf, "%i", ent); + entity.text(buf); + sprintf(buf, "%i", br); + brush.text(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)); + } + + window.destroy(); +} + +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; + +CopiedString g_strLastMap; +bool g_bLoadLastMap = false; + +void Map_Construct() +{ + GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff)); + GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY)); + GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush)); + GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected), + Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + + GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap)); + GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap)); + GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property(g_posMapInfoWnd)); + + PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences)); + + 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 0000000..62b4804 --- /dev/null +++ b/radiant/map.h @@ -0,0 +1,191 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..bcfbdf2 --- /dev/null +++ b/radiant/mru.cpp @@ -0,0 +1,248 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" + +const int 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) << "- " << 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(ui::MenuItem::from(MRU_items[0]), TRUE); + ui::MenuItem::from(MRU_items[MRU_used - 1]).show(); +} + +void MRU_Init() +{ + if (MRU_used > MRU_MAX) { + MRU_used = MRU_MAX; + } +} + +void MRU_AddWidget(ui::MenuItem 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(ui::MenuItem::from(MRU_items[0]), TRUE); + ui::MenuItem::from(MRU_items[pos]).show(); + } + } +} + +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) { + auto label = ui::Label::from(gtk_bin_get_child(GTK_BIN(MRU_items[0]))); + label.text("Recent Files"); + gtk_widget_set_sensitive(ui::MenuItem::from(MRU_items[0]), FALSE); + } else { + ui::MenuItem::from(MRU_items[MRU_used]).hide(); + } + } +} + + +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(ui::Menu menu) +{ + { + auto item = create_menu_item_with_mnemonic(menu, "_1", LoadMRUCaller(g_load_mru1)); + gtk_widget_set_sensitive(item, FALSE); + MRU_AddWidget(item, 0); + } + { + auto item = create_menu_item_with_mnemonic(menu, "_2", LoadMRUCaller(g_load_mru2)); + item.hide(); + MRU_AddWidget(item, 1); + } + { + auto item = create_menu_item_with_mnemonic(menu, "_3", LoadMRUCaller(g_load_mru3)); + item.hide(); + MRU_AddWidget(item, 2); + } + { + auto item = create_menu_item_with_mnemonic(menu, "_4", LoadMRUCaller(g_load_mru4)); + item.hide(); + MRU_AddWidget(item, 3); + } +} + +#include "preferencesystem.h" +#include "stringio.h" + +void MRU_Construct() +{ + GlobalPreferenceSystem().registerPreference("Count", make_property_string(MRU_used)); + + for (std::size_t i = 0; i != MRU_MAX; ++i) { + GlobalPreferenceSystem().registerPreference(MRU_keys[i], make_property_string(MRU_filenames[i])); + } + + MRU_Init(); +} + +void MRU_Destroy() +{ +} diff --git a/radiant/mru.h b/radiant/mru.h new file mode 100644 index 0000000..64d2cd3 --- /dev/null +++ b/radiant/mru.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 + */ + +#include + +#if !defined( INCLUDED_MRU_H ) +#define INCLUDED_MRU_H + +void MRU_AddFile(const char *str); + +void MRU_constructMenu(ui::Menu menu); + +void MRU_Construct(); + +void MRU_Destroy(); + +#endif diff --git a/radiant/multimon.cpp b/radiant/multimon.cpp new file mode 100644 index 0000000..99d6320 --- /dev/null +++ b/radiant/multimon.cpp @@ -0,0 +1,104 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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; + +LatchedValue g_Multimon_enableSysMenuPopups(false, "Floating windows sysmenu icons"); + +void MultiMonitor_constructPreferences(PreferencesPage &page) +{ + ui::CheckButton primary_monitor = page.appendCheckBox("Multi Monitor", "Start on Primary Monitor", + g_multimon_globals.m_bStartOnPrimMon); + ui::CheckButton popup = page.appendCheckBox( + "", "Disable system menu on popup windows", + make_property(g_Multimon_enableSysMenuPopups) + ); + 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", + make_property_string(g_multimon_globals.m_bStartOnPrimMon)); + GlobalPreferenceSystem().registerPreference("NoSysMenuPopups", + make_property_string(g_Multimon_enableSysMenuPopups.m_latched)); + + g_Multimon_enableSysMenuPopups.useLatched(); + + PreferencesDialog_addInterfacePreferences(makeCallbackF(MultiMonitor_constructPreferences)); +} + +void MultiMon_Destroy() +{ +} diff --git a/radiant/multimon.h b/radiant/multimon.h new file mode 100644 index 0000000..e1b3cf4 --- /dev/null +++ b/radiant/multimon.h @@ -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 + */ + +#if !defined( INCLUDED_MULTIMON_H ) +#define INCLUDED_MULTIMON_H + +#include "globaldefs.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 GDEF_OS_WINDOWS +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 0000000..d80e78e --- /dev/null +++ b/radiant/nullmodel.cpp @@ -0,0 +1,219 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 0000000..76a6c49 --- /dev/null +++ b/radiant/nullmodel.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_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 0000000..d5bf5a6 --- /dev/null +++ b/radiant/parse.cpp @@ -0,0 +1,52 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 0000000..bf9f2be --- /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 0000000..b329217 --- /dev/null +++ b/radiant/patch.cpp @@ -0,0 +1,2957 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define _USE_MATH_DEFINES +#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 < m_tess.m_numStrips; s++ ) + { + Array::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; h < height; h++) { + p1 = m_ctrl.data() + (h * row_stride); + for (w = 0; w < width; w++) { + p2 = p1 + col_stride; + p3 = p2 + col_stride; + p2->m_vertex = vector3_mid(p1->m_vertex, p3->m_vertex); + p1 = p3; + } + } + + controlPointsChanged(); +} + +void Patch::Smooth(EMatrixMajor mt) +{ + std::size_t w, h, width, height, row_stride, col_stride; + bool wrap; + PatchControl *p1, *p2, *p3, *p2b; + + 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; + } + + wrap = true; + for (h = 0; h < height; h++) { + p1 = m_ctrl.data() + (h * row_stride); + p2 = p1 + (2 * width) * col_stride; + //globalErrorStream() << "compare " << p1->m_vertex << " and " << p2->m_vertex << "\n"; + if (vector3_length_squared(vector3_subtracted(p1->m_vertex, p2->m_vertex)) > 1.0) { + //globalErrorStream() << "too far\n"; + wrap = false; + break; + } + } + + for (h = 0; h < height; h++) { + p1 = m_ctrl.data() + (h * row_stride) + col_stride; + for (w = 0; w < width - 1; w++) { + p2 = p1 + col_stride; + p3 = p2 + col_stride; + p2->m_vertex = vector3_mid(p1->m_vertex, p3->m_vertex); + p1 = p3; + } + if (wrap) { + p1 = m_ctrl.data() + (h * row_stride) + (2 * width - 1) * col_stride; + p2 = m_ctrl.data() + (h * row_stride); + p2b = m_ctrl.data() + (h * row_stride) + (2 * width) * col_stride; + p3 = m_ctrl.data() + (h * row_stride) + col_stride; + p2->m_vertex = p2b->m_vertex = vector3_mid(p1->m_vertex, p3->m_vertex); + } + } + + 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; i < width; i++) { + p[(bFirst) ? i : (width - 1) - i] = ctrlAt(nIndex, i).m_vertex; + } + } else { + for (i = 0; i < width; i++) { + p[(bFirst) ? i : (width - 1) - i] = ctrlAt(i, nIndex).m_vertex; + } + } + + patch->ConstructSeam(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; h < m_height; h++, tc += ti) { + for (w = 0, sc = 0.0f; w < m_width; w++, sc += si) { + pDest->m_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; w < m_width; w++, pWidth++) { + { + PatchControl *pHeight = pWidth; + for (std::size_t h = 0; h < m_height; h++, pHeight += m_width) { + pHeight->m_texcoord[0] = static_cast( tex ); + } + } + + if (w + 1 == m_width) { + break; + } + + { + PatchControl *pHeight = pWidth; + for (std::size_t h = 0; h < m_height; h++, pHeight += m_width) { + Vector3 v(vector3_subtracted(pHeight->m_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; h < m_height; h++, pHeight += m_width) { + { + PatchControl *pWidth = pHeight; + for (std::size_t w = 0; w < m_width; w++, pWidth++) { + pWidth->m_texcoord[1] = static_cast( tex ); + } + } + + if (h + 1 == m_height) { + break; + } + + { + PatchControl *pWidth = pHeight; + for (std::size_t w = 0; w < m_width; w++, pWidth++) { + Vector3 v(vector3_subtracted(pWidth->m_vertex, (pWidth + m_width)->m_vertex)); + double length = tex + (vector3_length(v) / fSize); + if (fabs(length) > texBest) { + texBest = length; + } + } + } + + tex = texBest; + } + } + + controlPointsChanged(); +} + +void Patch::IdentityColour() +{ + PatchControl *pCtrl = m_ctrl.data(); + + for (std::size_t h = 0; h < m_height; h++) { + for (std::size_t w = 0; w < m_width; w++, ++pCtrl) { + pCtrl->m_color = Vector4(1,1,1,1); + } + } +} + + +// 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(); + /* + if(GlobalSelectionSystem().countSelected() != 0) + { + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + PatchInstance* patch = Instance_getPatch(instance); + patch->m_selectable.isSelected(); + } + */ + 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 (bFirst) { + pos = height - 1; + } else { + pos = 2; + } + + 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]); + } + for (std::size_t i = 0; i != 4; ++i) { + r2a->m_color[i] = float_mid(c2b->m_color[i], p1->m_color[i]); + + r2b->m_color[i] = float_mid(c2a->m_color[i], c2b->m_color[i]); + + p2->m_color[i] = float_mid(r2a->m_color[i], r2b->m_color[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 (bFirst) { + pos = height - 3; + } else { + pos = 2; + } + 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; h < m_height; h++) { + vTmp[x] = vStart[x]; + for (std::size_t w = 0; w < m_width; w++, ++pCtrl) { + pCtrl->m_vertex = vTmp; + vTmp[x] += xAdj; + } + vTmp[y] += yAdj; + } + + IdentityColour(); + 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[0][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]; + } + } + break; + default: + ERROR_MESSAGE("this should be unreachable"); + return; + } + } else if (eType == eXactCylinder) { + int n = (width - 1) / 2; // n = number of segments + setDims(width, height); + + // vPos[0] = vector3_subtracted(aabb.origin, aabb.extents); + // vPos[1] = aabb.origin; + // vPos[2] = vector3_added(aabb.origin, aabb.extents); + + float f = 1 / cos(M_PI / n); + for (std::size_t i = 0; i < width; ++i) { + float angle = (M_PI * i) / n; // 0 to 2pi + float x = vPos[1][0] + (vPos[2][0] - vPos[1][0]) * cos(angle) * ((i & 1) ? f : 1.0f); + float y = vPos[1][1] + (vPos[2][1] - vPos[1][1]) * sin(angle) * ((i & 1) ? f : 1.0f); + for (std::size_t j = 0; j < height; ++j) { + float z = vPos[0][2] + (vPos[2][2] - vPos[0][2]) * (j / (float) (height - 1)); + PatchControl *v; + v = &m_ctrl.data()[j * width + i]; + v->m_vertex[0] = x; + v->m_vertex[1] = y; + v->m_vertex[2] = z; + } + } + } else if (eType == eXactCone) { + int n = (width - 1) / 2; // n = number of segments + setDims(width, height); + + // vPos[0] = vector3_subtracted(aabb.origin, aabb.extents); + // vPos[1] = aabb.origin; + // vPos[2] = vector3_added(aabb.origin, aabb.extents); + + float f = 1 / cos(M_PI / n); + for (std::size_t i = 0; i < width; ++i) { + float angle = (M_PI * i) / n; + for (std::size_t j = 0; j < height; ++j) { + float x = vPos[1][0] + (1.0f - (j / (float) (height - 1))) * (vPos[2][0] - vPos[1][0]) * cos(angle) * + ((i & 1) ? f : 1.0f); + float y = vPos[1][1] + (1.0f - (j / (float) (height - 1))) * (vPos[2][1] - vPos[1][1]) * sin(angle) * + ((i & 1) ? f : 1.0f); + float z = vPos[0][2] + (vPos[2][2] - vPos[0][2]) * (j / (float) (height - 1)); + PatchControl *v; + v = &m_ctrl.data()[j * width + i]; + v->m_vertex[0] = x; + v->m_vertex[1] = y; + v->m_vertex[2] = z; + } + } + } else if (eType == eXactSphere) { + int n = (width - 1) / 2; // n = number of segments (yaw) + int m = (height - 1) / 2; // m = number of segments (pitch) + setDims(width, height); + + // vPos[0] = vector3_subtracted(aabb.origin, aabb.extents); + // vPos[1] = aabb.origin; + // vPos[2] = vector3_added(aabb.origin, aabb.extents); + + float f = 1 / cos(M_PI / n); + float g = 1 / cos(M_PI / (2 * m)); + for (std::size_t i = 0; i < width; ++i) { + float angle = (M_PI * i) / n; + for (std::size_t j = 0; j < height; ++j) { + float angle2 = (M_PI * j) / (2 * m); + float x = vPos[1][0] + (vPos[2][0] - vPos[1][0]) * sin(angle2) * ((j & 1) ? g : 1.0f) * cos(angle) * + ((i & 1) ? f : 1.0f); + float y = vPos[1][1] + (vPos[2][1] - vPos[1][1]) * sin(angle2) * ((j & 1) ? g : 1.0f) * sin(angle) * + ((i & 1) ? f : 1.0f); + float z = vPos[1][2] + (vPos[2][2] - vPos[1][2]) * -cos(angle2) * ((j & 1) ? g : 1.0f); + PatchControl *v; + v = &m_ctrl.data()[j * width + i]; + v->m_vertex[0] = x; + v->m_vertex[1] = y; + v->m_vertex[2] = z; + } + } + } 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); + } + + IdentityColour(); + NaturalTexture(); +} + +void Patch::RenderDebug(RenderStateFlags state) const +{ + for (std::size_t i = 0; i < m_tess.m_numStrips; i++) { + glBegin(GL_QUAD_STRIP); + for (std::size_t j = 0; j < m_tess.m_lenStrips; j++) { + glNormal3fv(normal3f_to_array( + (m_tess.m_vertices.data() + m_tess.m_indices[i * m_tess.m_lenStrips + j])->normal)); + 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(); + } +} + +#include "patchdialog.h" + +bool PatchInspector_IsSelected(int x, int y); +void patch_draw_sphere(const Vector3 origin, float radius, int sides) +{ + if (radius <= 0) { + return; + } + + const double dt = c_2pi / static_cast( sides ); + const double dp = c_pi / static_cast( sides ); + + for (int i = 0; i <= sides - 1; ++i) { + for (int j = 0; j <= sides - 2; ++j) { + const double t = i * dt; + const double p = (j * dp) - (c_pi / 2.0); + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + } + } + + { + const double p = (sides - 1) * dp - (c_pi / 2.0); + for (int i = 0; i <= sides - 1; ++i) { + const double t = i * dt; + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius))); + glVertex3fv(vector3_to_array(v)); + } + + { + Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p), radius))); + glVertex3fv(vector3_to_array(v)); + } + } + } +} + +void RenderablePatchFixedSolid::RenderNormals() const +{ + const std::size_t width = m_tess.m_numStrips + 1; + const std::size_t height = m_tess.m_lenStrips >> 1; + + glBegin(GL_TRIANGLES); + for (std::size_t i = 0; i < width; i++) { + for (std::size_t j = 0; j < height; j++) { + Vector3 pos = (m_tess.m_vertices.data() + (j * width + i))->vertex; + Vector4 colour = (m_tess.m_vertices.data() + (j * width + i))->colour; + + /* color the currently selected bit */ + if (PatchInspector_IsSelected((int)i, (int)j)) { + glColor3f(1,0,0); + patch_draw_sphere(pos, 8, 4); + } else { + glColor3f(1,1,1); + patch_draw_sphere(pos, 2, 4); + } + + /*{ + 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))->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(), + 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(); + glColor3f( 1, 1, 1 ); +} + +const int DEGEN_0a = 0x01; +const int DEGEN_1a = 0x02; +const int DEGEN_2a = 0x04; +const int DEGEN_0b = 0x08; +const int DEGEN_1b = 0x10; +const int DEGEN_2b = 0x20; +const int SPLIT = 0x40; +const int 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 BezierInterpolate4(const Vector4 &start, Vector4 &left, Vector4 &mid, Vector4 &right, const Vector4 &end) +{ + left = vector4_mid(start, mid); + right = vector4_mid(mid, end); + mid = vector4_mid(left, right); +} + +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 Vector4 &colour_for_index(Array &vertices, std::size_t index) +{ + return reinterpret_cast( vertices[index].colour ); +} + +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 Vector4 &colour_for_index(const Array &vertices, std::size_t index) +{ + return reinterpret_cast( vertices[index].colour ); +} + +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), Vector4(0, 0, 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; + colour4f_to_vector4(p->colour) = point.m_color; + + 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, + Vector4 &colLeft, Vector4 &colMid, Vector4 &colRight, + 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; + Vector4 colTmp, colour_0_0, colour_0_1, colour_1_0, colour_1_1, colour_2_0, colour_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; + } + } + + + // colours + + BezierInterpolate4(colour_for_index(m_tess.m_vertices, offStartX + offStartY), + colour_0_0, + colour_for_index(m_tess.m_vertices, BX->index + offStartY), + colour_0_1, + colour_for_index(m_tess.m_vertices, offEndX + offStartY)); + + + BezierInterpolate4(colour_for_index(m_tess.m_vertices, offStartX + offEndY), + colour_2_0, + colour_for_index(m_tess.m_vertices, BX->index + offEndY), + colour_2_1, + colour_for_index(m_tess.m_vertices, offEndX + offEndY)); + + colTmp = colMid; + + BezierInterpolate4(colLeft, + colour_1_0, + colTmp, + colour_1_1, + colRight); + + if (!BezierCurveTree_isLeaf(BY)) { + colour_for_index(m_tess.m_vertices, BX->index + BY->index) = colTmp; + } + + + if (!BezierCurveTree_isLeaf(BX->left)) { + colour_for_index(m_tess.m_vertices, BX->left->index + offStartY) = colour_0_0; + colour_for_index(m_tess.m_vertices, BX->left->index + offEndY) = colour_2_0; + + if (!BezierCurveTree_isLeaf(BY)) { + colour_for_index(m_tess.m_vertices, BX->left->index + BY->index) = colour_1_0; + } + } + if (!BezierCurveTree_isLeaf(BX->right)) { + colour_for_index(m_tess.m_vertices, BX->right->index + offStartY) = colour_0_1; + colour_for_index(m_tess.m_vertices, BX->right->index + offEndY) = colour_2_1; + + if (!BezierCurveTree_isLeaf(BY)) { + colour_for_index(m_tess.m_vertices, BX->right->index + BY->index) = colour_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, + colour_0_0, colour_1_0, colour_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, + colour_0_1, colour_1_1, colour_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, + colLeft, colour_1_0, colTmp, + 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, + colTmp, colour_1_1, colRight, + 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) { + if (!m_subdivisions_x && !m_subdivisions_y) { + for (Array::iterator i = arrayLength.begin(); i != arrayLength.end(); ++i) { + *i = 2; + nArrayLength += *i; + } + } else { + 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 < m_tess.m_nArrayWidth; i++) { + for (std::size_t j = 0; j < m_tess.m_numStrips; j++) { + m_tess.m_indices[(j * m_tess.m_lenStrips) + i * 2] = RenderIndex(j * m_tess.m_nArrayWidth + i); + m_tess.m_indices[(j * m_tess.m_lenStrips) + i * 2 + 1] = RenderIndex( + (j + 1) * m_tess.m_nArrayWidth + i); + // reverse because radiant uses CULL_FRONT + //m_tess.m_indices[(j*m_tess.m_lenStrips)+i*2+1] = RenderIndex(j*m_tess.m_nArrayWidth+i); + //m_tess.m_indices[(j*m_tess.m_lenStrips)+i*2] = RenderIndex((j+1)*m_tess.m_nArrayWidth+i); + } + } + } else { + m_tess.m_numStrips = m_tess.m_nArrayWidth - 1; + m_tess.m_lenStrips = m_tess.m_nArrayHeight * 2; + + for (std::size_t i = 0; i < m_tess.m_nArrayHeight; i++) { + for (std::size_t j = 0; j < m_tess.m_numStrips; j++) { + m_tess.m_indices[(j * m_tess.m_lenStrips) + i * 2] = RenderIndex( + ((m_tess.m_nArrayHeight - 1) - i) * m_tess.m_nArrayWidth + j); + m_tess.m_indices[(j * m_tess.m_lenStrips) + i * 2 + 1] = RenderIndex( + ((m_tess.m_nArrayHeight - 1) - i) * m_tess.m_nArrayWidth + j + 1); + // reverse because radiant uses CULL_FRONT + //m_tess.m_indices[(j*m_tess.m_lenStrips)+i*2+1] = RenderIndex(((m_tess.m_nArrayHeight-1)-i)*m_tess.m_nArrayWidth+j); + //m_tess.m_indices[(j*m_tess.m_lenStrips)+i*2] = RenderIndex(((m_tess.m_nArrayHeight-1)-i)*m_tess.m_nArrayWidth+j+1); + + } + } + } + + if (m_patchDef3 && !m_subdivisions_x && !m_subdivisions_y) + { + for (std::size_t i = 0; i < m_width*m_height; i++) + { + ArbitraryMeshVertex &p = m_tess.m_vertices[i]; + PatchControl &cp = m_ctrlTransformed[i]; + p.vertex = vertex3f_for_vector3(cp.m_vertex); + p.texcoord = texcoord2f_for_vector2(cp.m_texcoord); + p.colour = colour4f_for_vector4(cp.m_color); + } + } + else + { + PatchControlIter pCtrl = m_ctrlTransformed.data(); + for (std::size_t j = 0, offStartY = 0; j + 1 < m_height; j += 2, pCtrl += (strideU + strideV)) { + // set up array offsets for this sub-patch + const bool leafY = (m_patchDef3) ? false : BezierCurveTree_isLeaf(m_tess.m_curveTreeV[j >> 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, + subMatrix[1][0]->m_color, subMatrix[1][1]->m_color, + subMatrix[1][2]->m_color, + 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, + subMatrix[0][1]->m_color, subMatrix[1][1]->m_color, + subMatrix[2][1]->m_color, + 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 0000000..99419f7 --- /dev/null +++ b/radiant/patch.h @@ -0,0 +1,2128 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 "globaldefs.h" +#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, + eXactCylinder, + eXactSphere, + eXactCone, +}; + +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; i < m_tess.m_numStrips; i++, strip_indices += m_tess.m_lenStrips) { + glDrawElements(GL_QUAD_STRIP, GLsizei(m_tess.m_lenStrips), RenderIndexTypeID, strip_indices); + } + } +}; + +class RenderablePatchFixedSolid : public OpenGLRenderable { + PatchTesselation &m_tess; +public: + RenderablePatchFixedSolid(PatchTesselation &tess) : m_tess(tess) + { + } + + void RenderNormals() const; + + void render(RenderStateFlags state) const + { + glNormalPointer(GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->normal); + glTexCoordPointer(2, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->texcoord); + glShadeModel(GL_SMOOTH); + glColorPointer(4, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->colour); + 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_tess.m_numStrips; i++, strip_indices += m_tess.m_lenStrips) { + glDrawElements(GL_QUAD_STRIP, GLsizei(m_tess.m_lenStrips), RenderIndexTypeID, strip_indices); + } + glShadeModel(GL_FLAT); + RenderNormals(); + } +}; + +class RenderablePatchSolid : public OpenGLRenderable { + PatchTesselation &m_tess; +public: + RenderablePatchSolid(PatchTesselation &tess) : m_tess(tess) + { + } + + void RenderNormals() const; + + void render(RenderStateFlags state) const + { + glNormalPointer(GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->normal); + glTexCoordPointer(2, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->texcoord); + glShadeModel(GL_SMOOTH); + glColorPointer(4, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->colour); + 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_tess.m_numStrips; i++, strip_indices += m_tess.m_lenStrips) { + glDrawElements(GL_QUAD_STRIP, GLsizei(m_tess.m_lenStrips), RenderIndexTypeID, strip_indices); + } + glShadeModel(GL_FLAT); + } +}; + +// parametric surface defined by quadratic bezier control curves +class Patch : + public XMLImporter, + public XMLExporter, + public TransformNode, + public Bounded, + public Cullable, + public Snappable, + public Undoable, + public Filterable, + public Nameable { + class xml_state_t { + public: + enum EState { + eDefault, + ePatch, + eMatrix, + eShader, + }; + + 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); + } + + private: + EState m_state; + StringOutputStream m_content; + }; + + std::vector 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, + bool patchDefWS, + 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_patchDefWS(patchDefWS), + 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; + bool m_patchDefWS; + 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; + bool m_patchDefWS; + std::size_t m_subdivisions_x; + std::size_t m_subdivisions_y; + PatchTesselation m_tess; +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; + + RenderablePatchSolid m_render_solid; + RenderablePatchFixedSolid m_render_solid_fixed; + 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_patchDefWS = 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_solid_fixed(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_solid_fixed(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_patchDefWS = other.m_patchDefWS; + 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_solid_fixed(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_patchDefWS = other.m_patchDefWS; + 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); + + if (m_patchDef3) { + renderer.addRenderable(m_render_solid_fixed, localToWorld); + } else { + 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 < m_tess.m_numStrips; s++) { + test.TestQuadStrip(vertexpointer_arbitrarymeshvertex(m_tess.m_vertices.data()), + IndexPointer(pIndex, m_tess.m_lenStrips), best); + pIndex += m_tess.m_lenStrips; + } + if (best.valid()) { + selector.addIntersection(best); + } + } + + void transform(const Matrix4 &matrix) + { + for (PatchControlIter i = m_ctrlTransformed.data(); + i != m_ctrlTransformed.data() + m_ctrlTransformed.size(); ++i) { + matrix4_transform_point(matrix, (*i).m_vertex); + } + + if (matrix4_handedness(matrix) == MATRIX4_LEFTHANDED) { + PatchControlArray_invert(m_ctrlTransformed, m_width, m_height); + } + UpdateCachedData(); + } + + void transformChanged() + { + m_transformChanged = true; + m_lightsChanged(); + SceneChangeNotify(); + } + + typedef MemberCaller 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 Smooth(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 IdentityColour(void); //sets all colours to 1,1,1,1 + + 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_patchDefWS, 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_patchDefWS = other.m_patchDefWS; + 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, + Vector4 &colLeft, Vector4 &colMid, Vector4 &colRight, + 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; c < patch.getWidth(); c++) { + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + for (std::size_t r = 0; r < patch.getHeight(); r++) { + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_vertex[0])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_vertex[1])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_vertex[2])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_texcoord[0])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_texcoord[1])); + + patch.ctrlAt(r, c).m_color = Vector4(1,1,1,1); //assume opaque white. + + if (patch.m_patchDefWS) { + //Temp Hack, to handle weird format... + if (Tokeniser_nextTokenMatches(tokeniser, ")")) + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + //End Temp Hack. + } + + if (Tokeniser_nextTokenMatches(tokeniser, ")")) + continue; + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_color[0])); + if (Tokeniser_nextTokenMatches(tokeniser, ")")) + continue; + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_color[1])); + if (Tokeniser_nextTokenMatches(tokeniser, ")")) + continue; + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_color[2])); + if (Tokeniser_nextTokenMatches(tokeniser, ")")) + continue; + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, patch.ctrlAt(r, c).m_color[3])); + + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + } + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + } + } + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + return true; +} + +inline bool Patch_importFooter(Patch &patch, Tokeniser &tokeniser) +{ + patch.controlPointsChanged(); + + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "}")); + + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "}")); + return true; +} + +class PatchTokenImporter : public MapImporter { + Patch &m_patch; +public: + PatchTokenImporter(Patch &patch) : m_patch(patch) + { + } + + bool importTokens(Tokeniser &tokeniser) + { + RETURN_FALSE_IF_FAIL(Patch_importHeader(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(Patch_importShader(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(Patch_importParams(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(Patch_importMatrix(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(Patch_importFooter(m_patch, tokeniser)); + + return true; + } +}; + +class PatchDoom3TokenImporter : public MapImporter { + Patch &m_patch; +public: + PatchDoom3TokenImporter(Patch &patch) : m_patch(patch) + { + } + + bool importTokens(Tokeniser &tokeniser) + { + RETURN_FALSE_IF_FAIL(Patch_importHeader(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(PatchDoom3_importShader(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(Patch_importParams(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(Patch_importMatrix(m_patch, tokeniser)); + RETURN_FALSE_IF_FAIL(Patch_importFooter(m_patch, tokeniser)); + + return true; + } +}; + +inline void Patch_exportHeader(const Patch &patch, TokenWriter &writer) +{ + writer.writeToken("{"); + writer.nextLine(); + + //if it has colours, use our postfix on the patchdef type (to prevent incompatible tools giving weird results - our parser doesn't really care for now...) + bool hascolours = false; + for (std::size_t c = 0; c < patch.getWidth() && !hascolours; c++) { + for (std::size_t r = 0; r < patch.getHeight(); r++) { + auto v = patch.ctrlAt(r, c); + if (v.m_color[0] != 1 || v.m_color[1] != 1 || v.m_color[2] != 1 || v.m_color[3] != 1) + { + hascolours = true; + break; + } + } + } + if (hascolours) { + writer.writeToken(patch.m_patchDef3 ? "patchDef3WS" : "patchDef2WS"); + } else { + writer.writeToken(patch.m_patchDef3 ? "patchDef3" : "patchDef2"); + } + writer.nextLine(); + writer.writeToken("{"); + writer.nextLine(); +} + +inline void Patch_exportShader(const Patch &patch, TokenWriter &writer) +{ + // write shader name + if (*(shader_get_textureName(patch.GetShader())) == '\0') { + writer.writeToken("NULL"); + } else { + writer.writeToken(shader_get_textureName(patch.GetShader())); + } + writer.nextLine(); +} + +inline void PatchDoom3_exportShader(const Patch &patch, TokenWriter &writer) +{ + // write shader name + if (*(shader_get_textureName(patch.GetShader())) == '\0') { + writer.writeString("_emptyname"); + } else { + writer.writeString(patch.GetShader()); + } + writer.nextLine(); +} + +inline void Patch_exportParams(const Patch &patch, TokenWriter &writer) +{ + // write matrix dimensions + writer.writeToken("("); + writer.writeUnsigned(patch.getWidth()); + writer.writeUnsigned(patch.getHeight()); + if (patch.m_patchDef3) { + writer.writeUnsigned(patch.m_subdivisions_x); + writer.writeUnsigned(patch.m_subdivisions_y); + } + writer.writeInteger(0); + writer.writeInteger(0); + writer.writeInteger(0); + writer.writeToken(")"); + writer.nextLine(); +} + +inline void Patch_exportMatrix(const Patch &patch, TokenWriter &writer) +{ + // write matrix + writer.writeToken("("); + writer.nextLine(); + for (std::size_t c = 0; c < patch.getWidth(); c++) { + writer.writeToken("("); + for (std::size_t r = 0; r < patch.getHeight(); r++) { + writer.writeToken("("); + auto v = patch.ctrlAt(r, c); + + writer.writeFloat(v.m_vertex[0]); + writer.writeFloat(v.m_vertex[1]); + writer.writeFloat(v.m_vertex[2]); + writer.writeFloat(v.m_texcoord[0]); + writer.writeFloat(v.m_texcoord[1]); + + if (v.m_color[0] != 1 || v.m_color[1] != 1 || v.m_color[2] != 1 || v.m_color[3] != 1) { + writer.writeFloat(v.m_color[0]); + writer.writeFloat(v.m_color[1]); + writer.writeFloat(v.m_color[2]); + writer.writeFloat(v.m_color[3]); + } + + writer.writeToken(")"); + } + writer.writeToken(")"); + writer.nextLine(); + } + writer.writeToken(")"); + writer.nextLine(); +} + +inline void Patch_exportFooter(const Patch &patch, TokenWriter &writer) +{ + writer.writeToken("}"); + writer.nextLine(); + writer.writeToken("}"); + writer.nextLine(); +} + +class PatchTokenExporter : public MapExporter { + const Patch &m_patch; +public: + PatchTokenExporter(Patch &patch) : m_patch(patch) + { + } + + void exportTokens(TokenWriter &writer) const + { + Patch_exportHeader(m_patch, writer); + Patch_exportShader(m_patch, writer); + Patch_exportParams(m_patch, writer); + Patch_exportMatrix(m_patch, writer); + Patch_exportFooter(m_patch, writer); + } +}; + +class PatchDoom3TokenExporter : public MapExporter { + const Patch &m_patch; +public: + PatchDoom3TokenExporter(Patch &patch) : m_patch(patch) + { + } + + void exportTokens(TokenWriter &writer) const + { + Patch_exportHeader(m_patch, writer); + PatchDoom3_exportShader(m_patch, writer); + Patch_exportParams(m_patch, writer); + Patch_exportMatrix(m_patch, writer); + Patch_exportFooter(m_patch, writer); + } +}; + +class PatchControlInstance { +public: + PatchControl *m_ctrl; + ObservedSelectable m_selectable; + + PatchControlInstance(PatchControl *ctrl, const SelectionChangeCallback &observer) + : m_ctrl(ctrl), m_selectable(observer) + { + } + + void testSelect(Selector &selector, SelectionTest &test) + { + SelectionIntersection best; + test.TestPoint(m_ctrl->m_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 MemberCaller SelectedChangedCaller; + + void selectedChangedComponent(const Selectable &selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable); + GlobalSelectionSystem().onComponentSelection(*this, selectable); + } + + typedef MemberCaller 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, bool patchWS) : + 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; + m_patch.m_patchDefWS = patchWS; + } + + 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 0000000..e62b296 --- /dev/null +++ b/radiant/patchdialog.cpp @@ -0,0 +1,1235 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +#include "itexdef.h" + +#include "debugging/debugging.h" + +#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 || patch.m_subdivisions_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 +} + +void Scene_PatchSetFixedSubdivisions(const PatchFixedSubdivisions &subdivisions) +{ + UndoableCommand command("patchSetFixedSubdivisions"); + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + Patch_setFixedSubdivisions(patch, subdivisions); + }); +} + + +class Subdivisions { +public: + gulong m_changeevent; + ui::ComboBoxText m_type; +// ui::CheckButton m_enabled; + ui::Entry m_horizontal; + ui::Entry m_vertical; + + Subdivisions() : m_type(ui::null), m_horizontal(ui::null), m_vertical(ui::null) + { + } + + void update() + { + PatchFixedSubdivisions subdivisions; + Scene_PatchGetFixedSubdivisions(subdivisions); + int mode = 0; + if (subdivisions.m_enabled) + { + if (subdivisions.m_x || subdivisions.m_y) + mode = 1; + else + mode = 2; + } + auto combo = GTK_COMBO_BOX_TEXT(m_type); + g_signal_handler_block(combo, m_changeevent); + gtk_combo_box_set_active(GTK_COMBO_BOX(combo), mode); + g_signal_handler_unblock(combo, m_changeevent); + + if (subdivisions.m_enabled && (subdivisions.m_x||subdivisions.m_y)) { + entry_set_int(m_horizontal, static_cast( subdivisions.m_x )); + entry_set_int(m_vertical, static_cast( subdivisions.m_y )); + gtk_widget_set_sensitive(m_horizontal, TRUE); + gtk_widget_set_sensitive(m_vertical, TRUE); + } else { + m_horizontal.text(""); + m_vertical.text(""); + gtk_widget_set_sensitive(m_horizontal, FALSE); + gtk_widget_set_sensitive(m_vertical, FALSE); + } + } + + void cancel() + { + update(); + } + + typedef MemberCaller CancelCaller; + + void apply() + { + auto patchtypetext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(m_type)); + int patchtype; + if (!strcasecmp(patchtypetext, "Explicit")) + patchtype = 2; + else if (!strcasecmp(patchtypetext, "Fixed")) + patchtype = 1; + else + patchtype = 0; + auto horiz = static_cast( entry_get_int(m_horizontal)); + auto vert = static_cast( entry_get_int(m_vertical)); + if (patchtype == 2) + horiz = vert = 0; + else if (!horiz || !vert) + horiz = vert = 4; + + Scene_PatchSetFixedSubdivisions( + PatchFixedSubdivisions( + patchtype != 0, + horiz, + vert + ) + ); + + SceneChangeNotify(); + } + + typedef MemberCaller ApplyCaller; + + static void OnSelchangeComboPatchType(ui::ComboBoxText toggle, Subdivisions *self) + { + self->apply(); + } +// static void applyGtk(ui::ToggleButton toggle, Subdivisions *self) +// { +// self->apply(); +// } +}; + +class PatchInspector : public Dialog { + ui::Window 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_fR; + float m_fG; + float m_fB; + float m_fA; +/* float m_fHScale; + float m_fHShift; + float m_fRotate; + float m_fVScale; + float m_fVShift; */ + int m_nCol; + int m_nRow; + ui::ComboBoxText m_pRowCombo{ui::null}; + ui::ComboBoxText m_pColCombo{ui::null}; + 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_fR = 1.f; + m_fG = 1.f; + m_fB = 1.f; + m_fA = 1.f; + 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 GetWidget().visible(); + } + +// 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; + +bool PatchInspector_IsSelected(int x, int y) +{ + if (g_PatchInspector.m_nCol == x && g_PatchInspector.m_nRow == y) { + return true; + } + return false; +} + +void PatchInspector_constructWindow(ui::Window 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(ui::Widget 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; + p.m_color[0] = g_PatchInspector.m_fR; + p.m_color[1] = g_PatchInspector.m_fG; + p.m_color[2] = g_PatchInspector.m_fB; + p.m_color[3] = g_PatchInspector.m_fA; + g_PatchInspector.m_Patch->controlPointsChanged(); + } + } +} + +static void OnSelchangeComboColRow(ui::Widget 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(); + g_PatchInspector.m_Patch->controlPointsChanged(); +} + +void Scene_PatchTileTexture_Selected(scene::Graph &graph, float s, float t) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.SetTextureRepeat(s, t); + }); + SceneChangeNotify(); +} + +static void OnBtnPatchdetails(ui::Widget widget, gpointer data) +{ + Patch_CapTexture(); +} + +static void OnBtnPatchfit(ui::Widget widget, gpointer data) +{ + Patch_FitTexture(); +} + +static void OnBtnPatchnatural(ui::Widget widget, gpointer data) +{ + Patch_NaturalTexture(); +} + +static void OnBtnPatchreset(ui::Widget widget, gpointer data) +{ + Patch_ResetTexture(); +} + +static void OnBtnPatchFlipX(ui::Widget widget, gpointer data) +{ + Patch_FlipTextureX(); +} + +static void OnBtnPatchFlipY(ui::Widget widget, gpointer data) +{ + Patch_FlipTextureY(); +} + +void Scene_PatchRotateTexture_Selected(scene::Graph &graph, float angle) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.RotateTexture(angle); + }); +} + +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) +{ + s = Patch_convertScale(s); + t = Patch_convertScale(t); + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.ScaleTexture(s, t); + }); +} + +void Scene_PatchTranslateTexture_Selected(scene::Graph &graph, float s, float t) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.TranslateTexture(s, t); + }); +} + +static void OnBtnPatchAutoCap(ui::Widget widget, gpointer data) +{ + Patch_AutoCapTexture(); +} + +static void OnSpinChanged(ui::Adjustment adj, gpointer data) +{ + texdef_t td; + + td.rotate = 0; + td.scale[0] = td.scale[1] = 0; + td.shift[0] = td.shift[1] = 0; + + if (gtk_adjustment_get_value(adj) == 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 (gtk_adjustment_get_value(adj) > 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 (gtk_adjustment_get_value(adj) > 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 (gtk_adjustment_get_value(adj) > 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 (gtk_adjustment_get_value(adj) > 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 (gtk_adjustment_get_value(adj) > 0) { + td.rotate = g_pi_globals.rotate; + } else { + td.rotate = -g_pi_globals.rotate; + } + } + + gtk_adjustment_set_value(adj, 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); + } else { + /* we don't want to do this on locked patches - eukara */ + // update the point-by-point view + OnSelchangeComboColRow(ui::root, 0); + } +} + +static gint OnDialogKey(ui::Widget widget, GdkEventKey *event, gpointer data) +{ + if (event->keyval == GDK_KEY_Return) { + OnApply(ui::root, 0); + return TRUE; + } else if (event->keyval == GDK_KEY_Escape) { + g_PatchInspector.GetPatchInfo(); + return TRUE; + } + return FALSE; +} + +// ============================================================================= +// PatchInspector class + +ui::Window PatchInspector::BuildDialog() +{ + ui::Window window = ui::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); + + + { + auto vbox = ui::VBox(FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); + vbox.show(); + window.add(vbox); + { + auto hbox = ui::HBox(FALSE, 5); + hbox.show(); + vbox.pack_start(hbox, TRUE, TRUE, 0); + { + auto vbox2 = ui::VBox(FALSE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0); + vbox2.show(); + hbox.pack_start(vbox2, TRUE, TRUE, 0); + { + auto frame = ui::Frame("Details"); + frame.show(); + vbox2.pack_start(frame, TRUE, TRUE, 0); + { + auto vbox3 = ui::VBox(FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5); + vbox3.show(); + frame.add(vbox3); + { + auto table = ui::Table(2, 2, FALSE); + table.show(); + vbox3.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + auto label = ui::Label("Row:"); + label.show(); + table.attach(label, {0, 1, 0, 1}, + {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)}, + {0, 0}); + } + { + auto label = ui::Label("Column:"); + label.show(); + table.attach(label, {1, 2, 0, 1}, + {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)}, + {0, 0}); + } + { + auto combo = ui::ComboBoxText(ui::New); + combo.connect("changed", G_CALLBACK(OnSelchangeComboColRow), this); + AddDialogData(combo, m_nRow); + + combo.show(); + table.attach(combo, {0, 1, 1, 2}, + {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)}, + {0, 0}); + combo.dimensions(60, -1); + m_pRowCombo = combo; + } + + { + auto combo = ui::ComboBoxText(ui::New); + combo.connect("changed", G_CALLBACK(OnSelchangeComboColRow), this); + AddDialogData(combo, m_nCol); + + combo.show(); + table.attach(combo, {1, 2, 1, 2}, + {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)}, + {0, 0}); + combo.dimensions(60, -1); + m_pColCombo = combo; + } + } + auto table = ui::Table(5, 2, FALSE); + table.show(); + vbox3.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + auto label = ui::Label("X:"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("Y:"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("Z:"); + label.show(); + table.attach(label, {0, 1, 2, 3}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("S:"); + label.show(); + table.attach(label, {0, 1, 3, 4}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("T:"); + label.show(); + table.attach(label, {0, 1, 4, 5}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("R:"); + label.show(); + table.attach(label, {0, 1, 5, 6}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("G:"); + label.show(); + table.attach(label, {0, 1, 6, 7}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("B:"); + label.show(); + table.attach(label, {0, 1, 7, 8}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto label = ui::Label("A:"); + label.show(); + table.attach(label, {0, 1, 8, 9}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fX); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fY); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fZ); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fS); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 4, 5}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fT); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 5, 6}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fR); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 6, 7}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fG); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 7, 8}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fB); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 8, 9}, {GTK_EXPAND | GTK_FILL, 0}); + AddDialogData(entry, m_fA); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + } + } + { + auto frame = ui::Frame("Tesselation"); + frame.show(); + vbox2.pack_start(frame, TRUE, TRUE, 0); + { + auto vbox3 = ui::VBox(FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5); + vbox3.show(); + frame.add(vbox3); + { + auto table = ui::Table(3, 2, FALSE); + table.show(); + vbox3.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + + { + auto label = ui::Label("Type"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto combo = ui::ComboBoxText(ui::New); + gtk_combo_box_text_append_text(combo, "Auto"); + gtk_combo_box_text_append_text(combo, "Fixed"); //patchDef3 + gtk_combo_box_text_append_text(combo, "Explicit"); + m_subdivisions.m_changeevent = combo.connect("changed", G_CALLBACK(Subdivisions::OnSelchangeComboPatchType), &m_subdivisions); +// AddDialogData(combo, m_nRow); + + combo.show(); + table.attach(combo, {1, 2, 0, 1}, + {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)}, + {0, 0}); + combo.dimensions(60, -1); + m_subdivisions.m_type = combo; + } + + /* + { + auto label = ui::Label("Fixed"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto check = ui::CheckButton::from(gtk_check_button_new()); + check.show(); + table.attach(check, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + m_subdivisions.m_enabled = check; + guint handler_id = check.connect("toggled", G_CALLBACK(&Subdivisions::applyGtk), + &m_subdivisions); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id)); + }*/ + { + auto label = ui::Label("Horizontal"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + m_subdivisions.m_horizontal = entry; + m_horizontalSubdivisionsEntry.connect(entry); + } + { + auto label = ui::Label("Vertical"); + label.show(); + table.attach(label, {0, 1, 2, 3}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0}); + m_subdivisions.m_vertical = entry; + m_verticalSubdivisionsEntry.connect(entry); + } + } + } + } + } + { + auto frame = ui::Frame("Texturing"); + frame.show(); + hbox.pack_start(frame, TRUE, TRUE, 0); + { + auto vbox2 = ui::VBox(FALSE, 5); + vbox2.show(); + frame.add(vbox2); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); + { + auto label = ui::Label("Name:"); + label.show(); + vbox2.pack_start(label, TRUE, TRUE, 0); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto entry = ui::Entry(ui::New); + // gtk_editable_set_editable (GTK_ENTRY (entry), false); + entry.show(); + vbox2.pack_start(entry, TRUE, TRUE, 0); + AddDialogData(entry, m_strName); + + entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + auto table = ui::Table(5, 4, FALSE); + table.show(); + vbox2.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + auto label = ui::Label("Horizontal Shift Step"); + label.show(); + table.attach(label, {2, 4, 0, 1}, {GTK_FILL | GTK_EXPAND, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label("Vertical Shift Step"); + label.show(); + table.attach(label, {2, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label("Horizontal Stretch Step"); + label.show(); + table.attach(label, {2, 3, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto button = ui::Button("Flip"); + button.show(); + table.attach(button, {3, 4, 2, 3}, {GTK_FILL, 0}); + button.connect("clicked", G_CALLBACK(OnBtnPatchFlipX), 0); + button.dimensions(60, -1); + } + { + auto label = ui::Label("Vertical Stretch Step"); + label.show(); + table.attach(label, {2, 3, 3, 4}, {GTK_FILL | GTK_EXPAND, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto button = ui::Button("Flip"); + button.show(); + table.attach(button, {3, 4, 3, 4}, {GTK_FILL, 0}); + button.connect("clicked", G_CALLBACK(OnBtnPatchFlipY), 0); + button.dimensions(60, -1); + } + { + auto label = ui::Label("Rotate Step"); + label.show(); + table.attach(label, {2, 4, 4, 5}, {GTK_FILL | GTK_EXPAND, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {0, 1, 0, 1}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + g_object_set_data(G_OBJECT(window), "hshift_entry", (void *) 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]); + + auto adj = ui::Adjustment(0, -8192, 8192, 1, 1, 0); + adj.connect("value_changed", G_CALLBACK(OnSpinChanged), (gpointer) entry); + g_object_set_data(G_OBJECT(window), "hshift_adj", (gpointer) adj); + + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 0, 1}, {0, 0}); + spin.dimensions(10, -1); + gtk_widget_set_can_focus(spin, false); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {0, 1, 1, 2}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + entry_set_float(entry, g_pi_globals.shift[1]); + + auto adj = ui::Adjustment(0, -8192, 8192, 1, 1, 0); + adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "vshift_adj", (gpointer) adj); + + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 1, 2}, {0, 0}); + spin.dimensions(10, -1); + gtk_widget_set_can_focus(spin, false); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {0, 1, 2, 3}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + entry_set_float(entry, g_pi_globals.scale[0]); + + auto adj = ui::Adjustment(0, -1000, 1000, 1, 1, 0); + adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "hscale_adj", (gpointer) adj); + + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 2, 3}, {0, 0}); + spin.dimensions(10, -1); + gtk_widget_set_can_focus(spin, false); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {0, 1, 3, 4}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + entry_set_float(entry, g_pi_globals.scale[1]); + + auto adj = ui::Adjustment(0, -1000, 1000, 1, 1, 0); + adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "vscale_adj", (gpointer) adj); + + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 3, 4}, {0, 0}); + spin.dimensions(10, -1); + gtk_widget_set_can_focus(spin, false); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {0, 1, 4, 5}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + entry_set_float(entry, g_pi_globals.rotate); + + auto adj = ui::Adjustment(0, -1000, 1000, 1, 1, + 0); // NOTE: Arnout - this really should be 360 but can't change it anymore as it could break existing maps + adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "rotate_adj", (gpointer) adj); + + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 4, 5}, {0, 0}); + spin.dimensions(10, -1); + gtk_widget_set_can_focus(spin, false); + } + } + auto hbox2 = ui::HBox(TRUE, 5); + hbox2.show(); + vbox2.pack_start(hbox2, TRUE, FALSE, 0); + { + auto button = ui::Button("Auto Cap"); + button.show(); + hbox2.pack_end(button, TRUE, FALSE, 0); + button.connect("clicked", G_CALLBACK(OnBtnPatchAutoCap), 0); + button.dimensions(60, -1); + } + { + auto button = ui::Button("CAP"); + button.show(); + hbox2.pack_end(button, TRUE, FALSE, 0); + button.connect("clicked", G_CALLBACK(OnBtnPatchdetails), 0); + button.dimensions(60, -1); + } + { + auto button = ui::Button("Set..."); + button.show(); + hbox2.pack_end(button, TRUE, FALSE, 0); + button.connect("clicked", G_CALLBACK(OnBtnPatchreset), 0); + button.dimensions(60, -1); + } + { + auto button = ui::Button("Natural"); + button.show(); + hbox2.pack_end(button, TRUE, FALSE, 0); + button.connect("clicked", G_CALLBACK(OnBtnPatchnatural), 0); + button.dimensions(60, -1); + } + { + auto button = ui::Button("Fit"); + button.show(); + hbox2.pack_end(button, TRUE, FALSE, 0); + button.connect("clicked", G_CALLBACK(OnBtnPatchfit), 0); + button.dimensions(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() +{ + { + 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_text_remove(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_text_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_text_remove(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_text_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; + m_fR = m_fG = m_fB = m_fA = 1; + + 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]; + m_fR = p.m_color[0]; + m_fG = p.m_color[1]; + m_fB = p.m_color[2]; + m_fA = p.m_color[3]; + } + } +} + + +void PatchInspector_SelectionChanged(const Selectable &selectable) +{ + PatchInspector_queueDraw(); +} + + +#include "preferencesystem.h" + + +void PatchInspector_Construct() +{ + GlobalCommands_insert("PatchInspector", makeCallbackF(PatchInspector_toggleShown), + Accelerator('S', (GdkModifierType) GDK_SHIFT_MASK)); + + GlobalPreferenceSystem().registerPreference("PatchWnd", make_property( + g_PatchInspector.m_position_tracker)); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Scale1", make_property_string(g_pi_globals.scale[0])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Scale2", make_property_string(g_pi_globals.scale[1])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Shift1", make_property_string(g_pi_globals.shift[0])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Shift2", make_property_string(g_pi_globals.shift[1])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Rotate", make_property_string(g_pi_globals.rotate)); + + typedef FreeCaller 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 0000000..692ccfe --- /dev/null +++ b/radiant/patchdialog.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 + */ + +#include + +#if !defined( INCLUDED_PATCHDIALOG_H ) +#define INCLUDED_PATCHDIALOG_H + +void PatchInspector_Construct(); + +void PatchInspector_Destroy(); + +void PatchInspector_constructWindow(ui::Window 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 0000000..d99d06c --- /dev/null +++ b/radiant/patchmanip.cpp @@ -0,0 +1,1137 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 +#include + +#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, int patchtype=0) +{ + Select_Delete(); + GlobalSelectionSystem().setSelectedAll(false); + + NodeSmartReference node(g_patchCreator->createPatch(patchtype!=0, true)); + Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(node); + + Patch *patch = Node_getPatch(node); + patch->SetShader(shader); + + if (patchtype==1) + { + patch->m_subdivisions_x = 4; + patch->m_subdivisions_y = 4; + } + + 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 PatchAutoCapTexture(Patch &patch) +{ + + AABB box = patch.localAABB(); + float x = box.extents.x(); + float y = box.extents.y(); + float z = box.extents.z(); + + int cap_direction = -1; + if (x < y && x < z) { + cap_direction = 0; + } else if (y < x && y < z) { + cap_direction = 1; + } else if (z < x && z < x) { + cap_direction = 2; + } + + if (cap_direction >= 0) { + patch.ProjectTexture(cap_direction); + } else { + patch.NaturalTexture(); + } +} + +void Patch_AutoCapTexture() +{ + UndoableCommand command("patchAutoCapTexture"); + Scene_forEachVisibleSelectedPatch(&PatchAutoCapTexture); + SceneChangeNotify(); +} + +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(false, true)); + Node_getTraversable(instance.path().parent())->insert(cap); + + Patch *cap_patch = Node_getPatch(cap); + patch.MakeCap(cap_patch, type, ROW, true); + cap_patch->SetShader(shader); + PatchAutoCapTexture(*cap_patch); + + scene::Path path(instance.path()); + path.pop(); + path.push(makeReference(cap.get())); + selectPath(path, true); + } + + { + NodeSmartReference cap(g_patchCreator->createPatch(false, true)); + Node_getTraversable(instance.path().parent())->insert(cap); + + Patch *cap_patch = Node_getPatch(cap); + patch.MakeCap(cap_patch, type, ROW, false); + cap_patch->SetShader(shader); + PatchAutoCapTexture(*cap_patch); + + scene::Path path(instance.path()); + path.pop(); + path.push(makeReference(cap.get())); + selectPath(path, true); + } +} + +typedef std::vector InstanceVector; + +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([&](PatchInstance &patch) { + instances.push_back(&patch); + }); + 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; +} + + +void Scene_PatchCapTexture_Selected(scene::Graph &graph) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.ProjectTexture(Patch::m_CycleCapIndex); + }); + Patch::m_CycleCapIndex = (Patch::m_CycleCapIndex == 0) ? 1 : (Patch::m_CycleCapIndex == 1) ? 2 : 0; + SceneChangeNotify(); +} + +void Scene_PatchFlipTexture_Selected(scene::Graph &graph, int axis) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.FlipTexture(axis); + }); +} + +void Scene_PatchNaturalTexture_Selected(scene::Graph &graph) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.NaturalTexture(); + }); + SceneChangeNotify(); +} + + +void Scene_PatchInsertRemove_Selected(scene::Graph &graph, bool bInsert, bool bColumn, bool bFirst) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.InsertRemove(bInsert, bColumn, bFirst); + }); +} + +void Scene_PatchInvert_Selected(scene::Graph &graph) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.InvertMatrix(); + }); +} + +void Scene_PatchRedisperse_Selected(scene::Graph &graph, EMatrixMajor major) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.Redisperse(major); + }); +} + +void Scene_PatchSmooth_Selected(scene::Graph &graph, EMatrixMajor major) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.Smooth(major); + }); +} + +void Scene_PatchTranspose_Selected(scene::Graph &graph) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.TransposeMatrix(); + }); +} + +void Scene_PatchSetShader_Selected(scene::Graph &graph, const char *name) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + patch.SetShader(name); + }); + SceneChangeNotify(); +} + +void Scene_PatchGetShader_Selected(scene::Graph &graph, CopiedString &name) +{ + Patch *patch = Scene_GetUltimateSelectedVisiblePatch(); + if (patch != 0) { + name = patch->GetShader(); + } +} + +void Scene_PatchSelectByShader(scene::Graph &graph, const char *name) +{ + Scene_forEachVisiblePatchInstance([&](PatchInstance &patch) { + if (shader_equal(patch.getPatch().GetShader(), name)) { + patch.setSelected(true); + } + }); +} + + +void Scene_PatchFindReplaceShader(scene::Graph &graph, const char *find, const char *replace) +{ + Scene_forEachVisiblePatch([&](Patch &patch) { + if (shader_equal(patch.GetShader(), find)) { + patch.SetShader(replace); + } + }); +} + +void Scene_PatchFindReplaceShader_Selected(scene::Graph &graph, const char *find, const char *replace) +{ + Scene_forEachVisibleSelectedPatch([&](Patch &patch) { + if (shader_equal(patch.GetShader(), find)) { + patch.SetShader(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 DoNewPatchDlg(EPatchPrefab prefab, int minrows, int mincols, int defrows, int defcols, int maxrows, int maxcols); + +void Patch_XactCylinder() +{ + UndoableCommand undo("patchCreateXactCylinder"); + + DoNewPatchDlg(eXactCylinder, 3, 7, 3, 13, 0, 0); +} + +void Patch_XactSphere() +{ + UndoableCommand undo("patchCreateXactSphere"); + + DoNewPatchDlg(eXactSphere, 5, 7, 7, 13, 0, 0); +} + +void Patch_XactCone() +{ + UndoableCommand undo("patchCreateXactCone"); + + DoNewPatchDlg(eXactCone, 3, 7, 3, 13, 0, 0); +} + +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_Sphere() +{ + UndoableCommand undo("patchCreateSphere"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), + TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eSphere, + 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 Patch_Plane() +{ + UndoableCommand undo("patchCreatePlane"); + + DoNewPatchDlg(ePlane, 3, 3, 3, 3, 0, 0); +} + +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(), ROW); +} + +void Patch_RedisperseCols() +{ + UndoableCommand undo("patchRedisperseColumns"); + + Scene_PatchRedisperse_Selected(GlobalSceneGraph(), COL); +} + +void Patch_SmoothRows() +{ + UndoableCommand undo("patchSmoothRows"); + + Scene_PatchSmooth_Selected(GlobalSceneGraph(), ROW); +} + +void Patch_SmoothCols() +{ + UndoableCommand undo("patchSmoothColumns"); + + Scene_PatchSmooth_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()); +} + +void Patch_CapTexture() +{ + UndoableCommand command("patchCapTexture"); + + Scene_PatchCapTexture_Selected(GlobalSceneGraph()); +} + +void Patch_ResetTexture() +{ + float fx, fy; + if (DoTextureLayout(&fx, &fy) == eIDOK) { + UndoableCommand command("patchTileTexture"); + Scene_PatchTileTexture_Selected(GlobalSceneGraph(), fx, fy); + } +} + +void Patch_FitTexture() +{ + UndoableCommand command("patchFitTexture"); + + Scene_PatchTileTexture_Selected(GlobalSceneGraph(), 1, 1); +} + +#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(makeCallbackF(Patch_constructPage)); +} + + +#include "preferencesystem.h" + +void PatchPreferences_construct() +{ + GlobalPreferenceSystem().registerPreference("Subdivisions", make_property_string(g_PatchSubdivideThreshold)); +} + + +#include "generic/callback.h" + +void Patch_registerCommands() +{ + GlobalCommands_insert("InvertCurveTextureX", makeCallbackF(Patch_FlipTextureX), + Accelerator('I', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("InvertCurveTextureY", makeCallbackF(Patch_FlipTextureY), + Accelerator('I', (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("NaturalizePatch", makeCallbackF(Patch_NaturalTexture), + Accelerator('N', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("PatchCylinder", makeCallbackF(Patch_Cylinder)); + GlobalCommands_insert("PatchDenseCylinder", makeCallbackF(Patch_DenseCylinder)); + GlobalCommands_insert("PatchVeryDenseCylinder", makeCallbackF(Patch_VeryDenseCylinder)); + GlobalCommands_insert("PatchSquareCylinder", makeCallbackF(Patch_SquareCylinder)); + GlobalCommands_insert("PatchXactCylinder", makeCallbackF(Patch_XactCylinder)); + GlobalCommands_insert("PatchXactSphere", makeCallbackF(Patch_XactSphere)); + GlobalCommands_insert("PatchXactCone", makeCallbackF(Patch_XactCone)); + GlobalCommands_insert("PatchEndCap", makeCallbackF(Patch_Endcap)); + GlobalCommands_insert("PatchBevel", makeCallbackF(Patch_Bevel)); + GlobalCommands_insert("PatchSquareBevel", makeCallbackF(Patch_SquareBevel)); + GlobalCommands_insert("PatchSquareEndcap", makeCallbackF(Patch_SquareEndcap)); + GlobalCommands_insert("PatchCone", makeCallbackF(Patch_Cone)); + GlobalCommands_insert("PatchSphere", makeCallbackF(Patch_Sphere)); + GlobalCommands_insert("SimplePatchMesh", makeCallbackF(Patch_Plane), + Accelerator('P', (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("PatchInsertInsertColumn", makeCallbackF(Patch_InsertInsertColumn), + Accelerator(GDK_KEY_KP_Add, (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("PatchInsertAddColumn", makeCallbackF(Patch_InsertAddColumn)); + GlobalCommands_insert("PatchInsertInsertRow", makeCallbackF(Patch_InsertInsertRow), + Accelerator(GDK_KEY_KP_Add, (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("PatchInsertAddRow", makeCallbackF(Patch_InsertAddRow)); + GlobalCommands_insert("PatchDeleteFirstColumn", makeCallbackF(Patch_DeleteFirstColumn)); + GlobalCommands_insert("PatchDeleteLastColumn", makeCallbackF(Patch_DeleteLastColumn), + Accelerator(GDK_KEY_KP_Subtract, (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("PatchDeleteFirstRow", makeCallbackF(Patch_DeleteFirstRow), + Accelerator(GDK_KEY_KP_Subtract, (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("PatchDeleteLastRow", makeCallbackF(Patch_DeleteLastRow)); + GlobalCommands_insert("InvertCurve", makeCallbackF(Patch_Invert), + Accelerator('I', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("RedisperseRows", makeCallbackF(Patch_RedisperseRows), + Accelerator('E', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("RedisperseCols", makeCallbackF(Patch_RedisperseCols), + Accelerator('E', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("SmoothRows", makeCallbackF(Patch_SmoothRows), + Accelerator('W', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("SmoothCols", makeCallbackF(Patch_SmoothCols), + Accelerator('W', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("MatrixTranspose", makeCallbackF(Patch_Transpose), + Accelerator('M', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("CapCurrentCurve", makeCallbackF(Patch_Cap), + Accelerator('C', (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("CycleCapTexturePatch", makeCallbackF(Patch_CycleProjection), + Accelerator('N', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("MakeOverlayPatch", makeCallbackF(Patch_OverlayOn), Accelerator('Y')); + GlobalCommands_insert("ClearPatchOverlays", makeCallbackF(Patch_OverlayOff), + Accelerator('L', (GdkModifierType) GDK_CONTROL_MASK)); +} + +void Patch_constructToolbar(ui::Toolbar toolbar) +{ + toolbar_append_button(toolbar, "Put caps on the current patch (SHIFT + C)", "cap_curve.xpm", "CapCurrentCurve"); +} + +void Patch_constructMenu(ui::Menu menu) +{ + create_menu_item_with_mnemonic(menu, "Cylinder", "PatchCylinder"); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "More Cylinders"); + /*if (g_Layout_enableOpenStepUX.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"); + create_menu_item_with_mnemonic(menu_in_menu, "Exact Cylinder...", "PatchXactCylinder"); + } + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "End cap", "PatchEndCap"); + create_menu_item_with_mnemonic(menu, "Bevel", "PatchBevel"); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "More End caps, Bevels"); + /*if (g_Layout_enableOpenStepUX.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"); + create_menu_item_with_mnemonic(menu, "Exact Cone...", "PatchXactCone"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Sphere", "PatchSphere"); + create_menu_item_with_mnemonic(menu, "Exact Sphere...", "PatchXactSphere"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Simple Patch Mesh...", "SimplePatchMesh"); + menu_separator(menu); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Insert"); + /*if (g_Layout_enableOpenStepUX.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"); + } + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Delete"); + /*if (g_Layout_enableOpenStepUX.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); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Matrix"); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu_in_menu); + }*/ + create_menu_item_with_mnemonic(menu_in_menu, "Invert", "InvertCurve"); + auto menu_3 = create_sub_menu_with_mnemonic(menu_in_menu, "Re-disperse"); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu_3); + }*/ + create_menu_item_with_mnemonic(menu_3, "Rows", "RedisperseRows"); + create_menu_item_with_mnemonic(menu_3, "Columns", "RedisperseCols"); + auto menu_4 = create_sub_menu_with_mnemonic(menu_in_menu, "Smooth"); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu_4); + }*/ + create_menu_item_with_mnemonic(menu_4, "Rows", "SmoothRows"); + create_menu_item_with_mnemonic(menu_4, "Columns", "SmoothCols"); + 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); + { + auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Overlay"); + /*if (g_Layout_enableOpenStepUX.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 "gtkutil/dialog.h" +#include "gtkutil/widget.h" + +void DoNewPatchDlg(EPatchPrefab prefab, int minrows, int mincols, int defrows, int defcols, int maxrows, int maxcols) +{ + ModalDialog dialog; + + ui::Window window = MainFrame_getWindow().create_dialog_window("Patch density", G_CALLBACK(dialog_delete_callback), + &dialog); + + auto accel = ui::AccelGroup(ui::New); + window.add_accel_group(accel); + auto width = ui::ComboBoxText(ui::New); + auto height = ui::ComboBoxText(ui::New); + auto type = ui::ComboBoxText(ui::New); + { + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + { + auto table = create_dialog_table(3, 2, 4, 4); + hbox.pack_start(table, TRUE, TRUE, 0); + { + auto label = ui::Label("Width:"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label("Height:"); + label.show(); + table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + auto label = ui::Label("Type:"); + label.show(); + table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0}); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + + { + auto combo = width; +#define D_ITEM(x) if ( x >= mincols && ( !maxcols || x <= maxcols ) ) gtk_combo_box_text_append_text( combo, #x ) + D_ITEM(3); + D_ITEM(5); + D_ITEM(7); + D_ITEM(9); + D_ITEM(11); + D_ITEM(13); + D_ITEM(15); + D_ITEM(17); + D_ITEM(19); + D_ITEM(21); + D_ITEM(23); + D_ITEM(25); + D_ITEM(27); + D_ITEM(29); + D_ITEM(31); // MAX_PATCH_SIZE is 32, so we should be able to do 31... +#undef D_ITEM + combo.show(); + table.attach(combo, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto combo = height; +#define D_ITEM(x) if ( x >= minrows && ( !maxrows || x <= maxrows ) ) gtk_combo_box_text_append_text( combo, #x ) + D_ITEM(3); + D_ITEM(5); + D_ITEM(7); + D_ITEM(9); + D_ITEM(11); + D_ITEM(13); + D_ITEM(15); + D_ITEM(17); + D_ITEM(19); + D_ITEM(21); + D_ITEM(23); + D_ITEM(25); + D_ITEM(27); + D_ITEM(29); + D_ITEM(31); // MAX_PATCH_SIZE is 32, so we should be able to do 31... +#undef D_ITEM + combo.show(); + table.attach(combo, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + } + { + auto combo = type; +#define D_ITEM(x) gtk_combo_box_text_append_text( combo, x ) + D_ITEM("Auto"); + D_ITEM("Fixed"); + D_ITEM("Explicit"); +#undef D_ITEM + combo.show(); + table.attach(combo, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0}); + } + } + + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_grab_focus(button); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + } + } + + // Initialize dialog + gtk_combo_box_set_active(width, (defcols - mincols) / 2); + gtk_combo_box_set_active(height, (defrows - minrows) / 2); + gtk_combo_box_set_active(type, 0); + + if (modal_dialog_show(window, dialog) == eIDOK) { + int w = gtk_combo_box_get_active(width) * 2 + mincols; + int h = gtk_combo_box_get_active(height) * 2 + minrows; + int t = gtk_combo_box_get_active(type); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), + TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), prefab, + GlobalXYWnd_getCurrentViewType(), w, h, t); + } + + window.destroy(); +} + + +EMessageBoxReturn DoCapDlg(ECapDialog *type) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + ui::Widget bevel{ui::null}; + ui::Widget ibevel{ui::null}; + ui::Widget endcap{ui::null}; + ui::Widget iendcap{ui::null}; + ui::Widget cylinder{ui::null}; + + ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Cap", dialog); + + auto accel_group = ui::AccelGroup(ui::New); + window.add_accel_group(accel_group); + + { + auto hbox = create_dialog_hbox(4, 4); + window.add(hbox); + + { + // Gef: Added a vbox to contain the toggle buttons + auto radio_vbox = create_dialog_vbox(4); + hbox.add(radio_vbox); + + { + auto table = ui::Table(5, 2, FALSE); + table.show(); + radio_vbox.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + + { + auto image = new_local_image("cap_bevel.xpm"); + image.show(); + table.attach(image, {0, 1, 0, 1}, {GTK_FILL, 0}); + } + { + auto image = new_local_image("cap_endcap.xpm"); + image.show(); + table.attach(image, {0, 1, 1, 2}, {GTK_FILL, 0}); + } + { + auto image = new_local_image("cap_ibevel.xpm"); + image.show(); + table.attach(image, {0, 1, 2, 3}, {GTK_FILL, 0}); + } + { + auto image = new_local_image("cap_iendcap.xpm"); + image.show(); + table.attach(image, {0, 1, 3, 4}, {GTK_FILL, 0}); + } + { + auto image = new_local_image("cap_cylinder.xpm"); + image.show(); + table.attach(image, {0, 1, 4, 5}, {GTK_FILL, 0}); + } + + GSList *group = 0; + { + ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Bevel")); + button.show(); + table.attach(button, {1, 2, 0, 1}, {GTK_FILL | GTK_EXPAND, 0}); + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); + + bevel = button; + } + { + ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Endcap")); + button.show(); + table.attach(button, {1, 2, 1, 2}, {GTK_FILL | GTK_EXPAND, 0}); + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); + + endcap = button; + } + { + ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Inverted Bevel")); + button.show(); + table.attach(button, {1, 2, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}); + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); + + ibevel = button; + } + { + ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Inverted Endcap")); + button.show(); + table.attach(button, {1, 2, 3, 4}, {GTK_FILL | GTK_EXPAND, 0}); + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); + + iendcap = button; + } + { + ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Cylinder")); + button.show(); + table.attach(button, {1, 2, 4, 5}, {GTK_FILL | GTK_EXPAND, 0}); + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); + + cylinder = button; + } + } + } + + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, FALSE, FALSE, 0); + { + auto button = create_modal_dialog_button("OK", ok_button); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Return, (GdkModifierType) 0, + GTK_ACCEL_VISIBLE); + } + { + auto button = create_modal_dialog_button("Cancel", cancel_button); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_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; + } + } + + window.destroy(); + + return ret; +} diff --git a/radiant/patchmanip.h b/radiant/patchmanip.h new file mode 100644 index 0000000..6ac361b --- /dev/null +++ b/radiant/patchmanip.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "string/stringfwd.h" + +void Patch_registerCommands(); + +void Patch_constructToolbar(ui::Toolbar toolbar); + +void Patch_constructMenu(ui::Menu 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(); + +void Patch_NaturalTexture(); + +void Patch_CapTexture(); + +void Patch_ResetTexture(); + +void Patch_FitTexture(); + +void Patch_FlipTextureX(); + +void Patch_FlipTextureY(); + +void Patch_AutoCapTexture(); + +class PatchCreator; + +extern PatchCreator *g_patchCreator; + +#endif diff --git a/radiant/patchmodule.cpp b/radiant/patchmodule.cpp new file mode 100644 index 0000000..88e2abe --- /dev/null +++ b/radiant/patchmodule.cpp @@ -0,0 +1,247 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 = 31; // matching q3map2 + } +} + +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(bool def3, bool ws) + { + return (new PatchNodeQuake3(def3, ws))->node(); + } +}; + +Quake3PatchCreator g_Quake3PatchCreator; + +PatchCreator &GetQuake3PatchCreator() +{ + return g_Quake3PatchCreator; +} + +class Doom3PatchCreator : public CommonPatchCreator { +public: + scene::Node &createPatch(bool def3, bool ws) + { //these are ALWAYS def3... + return (new PatchNodeDoom3(true, ws))->node(); + } +}; + +Doom3PatchCreator g_Doom3PatchCreator; + +PatchCreator &GetDoom3PatchCreator() +{ + return g_Doom3PatchCreator; +} + +class Doom3PatchDef2Creator : public CommonPatchCreator { +public: + scene::Node &createPatch(bool def3, bool ws) + { + return (new PatchNodeDoom3(def3, ws))->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 0000000..c97cb7f --- /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 0000000..fc77af1 --- /dev/null +++ b/radiant/plugin.cpp @@ -0,0 +1,356 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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()); +} + +const char *getGameFile() +{ + return g_GamesDialog.m_sGameFile.c_str(); +} + +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.getGameFile = &getGameFile; + 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.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 0000000..4b0992a --- /dev/null +++ b/radiant/plugin.h @@ -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 + */ + +#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 0000000..e951b41 --- /dev/null +++ b/radiant/pluginapi.cpp @@ -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 + */ + +#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( 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 0000000..f145c09 --- /dev/null +++ b/radiant/pluginapi.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 + */ + +#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 0000000..33d7a2f --- /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(ui::Widget 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(ui::Widget 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(ui::Widget 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, ui::Widget main_window) +{ + class AddPluginVisitor : public PluginModules::Visitor { + CPluginSlots &m_slots; + ui::Widget m_main_window; + public: + AddPluginVisitor(CPluginSlots &slots, ui::Widget 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(ui::Widget 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 0000000..61334ea --- /dev/null +++ b/radiant/pluginmanager.h @@ -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 + */ + +#if !defined( INCLUDED_PLUGINMANAGER_H ) +#define INCLUDED_PLUGINMANAGER_H + +#include +#include + + +/*! + \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(ui::Widget 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 0000000..adaa173 --- /dev/null +++ b/radiant/pluginmenu.cpp @@ -0,0 +1,181 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 + +#include "stream/textstream.h" + +#include "gtkutil/pointer.h" +#include "gtkutil/menu.h" + +#include "pluginmanager.h" +#include "mainframe.h" +#include "preferences.h" + + +int m_nNextPlugInID = 0; + +void plugin_activated(ui::Widget widget, gpointer data) +{ + const char *str = (const char *) g_object_get_data(G_OBJECT(widget), "command"); + GetPlugInMgr().Dispatch(gpointer_to_int(data), str); +} + +#include + +void PlugInMenu_Add(ui::Menu plugin_menu, IPlugIn *pPlugIn) +{ + ui::Widget item{ui::null}, parent{ui::null}; + ui::Menu menu{ui::null}, subMenu{ui::null}; + const char *menuText, *menuCommand; + std::stack menuStack; + + parent = ui::MenuItem(pPlugIn->getMenuName()); + parent.show(); + plugin_menu.add(parent); + + std::size_t nCount = pPlugIn->getCommandCount(); + if (nCount > 0) { + menu = ui::Menu(ui::New); + /*if (g_Layout_enableOpenStepUX.m_value) { + menu_tearoff(menu); + }*/ + while (nCount > 0) { + menuText = pPlugIn->getCommandTitle(--nCount); + menuCommand = pPlugIn->getCommand(nCount); + + if (menuText != 0 && strlen(menuText) > 0) { + if (!strcmp(menuText, "-")) { + item = ui::Widget::from(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 = ui::MenuItem(menuText); + item.show(); + menu.add(item); + + subMenu = ui::Menu(ui::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 = ui::MenuItem(menuText); + g_object_set_data(G_OBJECT(item), "command", + const_cast( static_cast( menuCommand ))); + item.connect("activate", G_CALLBACK(plugin_activated), gint_to_pointer(m_nNextPlugInID)); + } + item.show(); + menu.add(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); + } +} + +ui::Menu g_plugins_menu{ui::null}; +ui::MenuItem g_plugins_menu_separator{ui::null}; + +void PluginsMenu_populate() +{ + class PluginsMenuConstructor : public PluginsVisitor { + ui::Menu m_menu; + public: + PluginsMenuConstructor(ui::Menu 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_get_children(GTK_CONTAINER(g_plugins_menu)), + g_plugins_menu_separator._handle); + while (lst->next) { + g_plugins_menu.remove(ui::Widget::from(lst->next->data)); + lst = g_list_find(gtk_container_get_children(GTK_CONTAINER(g_plugins_menu)), g_plugins_menu_separator._handle); + } +} + +ui::MenuItem create_plugins_menu() +{ + // Plugins menu + auto plugins_menu_item = new_sub_menu_item_with_mnemonic("_Plugins"); + auto menu = ui::Menu::from(gtk_menu_item_get_submenu(plugins_menu_item)); + /*if (g_Layout_enableOpenStepUX.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", makeCallbackF(Restart) ); + + // 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 0000000..1d2f8b6 --- /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 + */ + +#include + +#if !defined( INCLUDED_PLUGINMENU_H ) +#define INCLUDED_PLUGINMENU_H + +ui::MenuItem create_plugins_menu(); + +void PluginsMenu_populate(); + +void PluginsMenu_clear(); + +#endif diff --git a/radiant/plugintoolbar.cpp b/radiant/plugintoolbar.cpp new file mode 100644 index 0000000..32ecda9 --- /dev/null +++ b/radiant/plugintoolbar.cpp @@ -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 + */ + +#include "plugintoolbar.h" + +#include + +#include "itoolbar.h" +#include "modulesystem.h" + +#include "stream/stringstream.h" +#include "gtkutil/image.h" +#include "gtkutil/container.h" + +#include "mainframe.h" +#include "plugin.h" + +ui::Image new_plugin_image(const char *filename) +{ + { + StringOutputStream fullpath(256); + fullpath << GameToolsPath_get() << g_pluginsDir << "bitmaps/" << filename; + if (auto image = image_new_from_file_with_mask(fullpath.c_str())) { return image; } + } + + { + StringOutputStream fullpath(256); + fullpath << AppPath_get() << g_pluginsDir << "bitmaps/" << filename; + if (auto image = image_new_from_file_with_mask(fullpath.c_str())) { return image; } + } + + { + StringOutputStream fullpath(256); + fullpath << AppPath_get() << g_modulesDir << "bitmaps/" << filename; + if (auto image = image_new_from_file_with_mask(fullpath.c_str())) { return image; } + } + + return image_new_missing(); +} + +void +toolbar_insert(ui::Toolbar toolbar, const char *icon, const char *text, const char *tooltip, IToolbarButton::EType type, + GCallback handler, gpointer data) +{ + if (type == IToolbarButton::eSpace) { + auto it = ui::ToolItem::from(gtk_separator_tool_item_new()); + it.show(); + toolbar.add(it); + return; + } + if (type == IToolbarButton::eButton) { + auto button = ui::ToolButton::from(gtk_tool_button_new(new_plugin_image(icon), text)); + gtk_widget_set_tooltip_text(button, tooltip); + gtk_widget_show_all(button); + button.connect("clicked", G_CALLBACK(handler), data); + toolbar.add(button); + return; + } + if (type == IToolbarButton::eToggleButton) { + auto button = ui::ToolButton::from(gtk_toggle_tool_button_new()); + gtk_tool_button_set_icon_widget(button, new_plugin_image(icon)); + gtk_tool_button_set_label(button, text); + gtk_widget_set_tooltip_text(button, tooltip); + gtk_widget_show_all(button); + button.connect("toggled", G_CALLBACK(handler), data); + toolbar.add(button); + return; + } + ERROR_MESSAGE("invalid toolbar button type"); +} + +void ActivateToolbarButton(ui::ToolButton widget, gpointer data) +{ + (const_cast( reinterpret_cast( data )))->activate(); +} + +void PlugInToolbar_AddButton(ui::Toolbar toolbar, const IToolbarButton *button) +{ + toolbar_insert(toolbar, button->getImage(), button->getText(), button->getTooltip(), button->getType(), + G_CALLBACK(ActivateToolbarButton), + reinterpret_cast( const_cast( button ))); +} + +ui::Toolbar g_plugin_toolbar{ui::null}; + +void PluginToolbar_populate() +{ + class AddToolbarItemVisitor : public ToolbarModules::Visitor { + ui::Toolbar m_toolbar; + public: + AddToolbarItemVisitor(ui::Toolbar 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; i < count; ++i) { + PlugInToolbar_AddButton(m_toolbar, table.m_pfnGetToolbarButton(i)); + } + } + + } visitor(g_plugin_toolbar); + + Radiant_getToolbarModules().foreachModule(visitor); +} + +void PluginToolbar_clear() +{ + container_remove_all(g_plugin_toolbar); +} + +ui::Toolbar create_plugin_toolbar() +{ + + auto toolbar = ui::Toolbar::from(gtk_toolbar_new()); + gtk_orientable_set_orientation(GTK_ORIENTABLE(toolbar), GTK_ORIENTATION_HORIZONTAL); + gtk_toolbar_set_style(toolbar, GTK_TOOLBAR_ICONS); + toolbar.show(); + + g_plugin_toolbar = toolbar; + + PluginToolbar_populate(); + + return toolbar; +} + +/* blerugh - eukara */ +void PluginToolbar_AddToMain(ui::Toolbar toolbar) +{ + + g_plugin_toolbar = toolbar; + + PluginToolbar_populate(); +} diff --git a/radiant/plugintoolbar.h b/radiant/plugintoolbar.h new file mode 100644 index 0000000..9445cdf --- /dev/null +++ b/radiant/plugintoolbar.h @@ -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 + +#if !defined( INCLUDED_PLUGINTOOLBAR_H ) +#define INCLUDED_PLUGINTOOLBAR_H + +ui::Toolbar create_plugin_toolbar(); + +void PluginToolbar_populate(); + +void PluginToolbar_clear(); + +ui::Image new_plugin_image(const char *filename); // filename is relative to plugin bitmaps path + +#endif diff --git a/radiant/points.cpp b/radiant/points.cpp new file mode 100644 index 0000000..6e29109 --- /dev/null +++ b/radiant/points.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 + */ + +/* + 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. + */ + +#include "points.h" + +#include "debugging/debugging.h" + +#include "irender.h" +#include "igl.h" +#include "renderable.h" + +#include "stream/stringstream.h" +#include "os/path.h" +#include "os/file.h" +#include "cmdlib.h" + +#include "map.h" +#include "qe3.h" +#include "camwindow.h" +#include "xywindow.h" +#include "xmlstuff.h" +#include "mainframe.h" +#include "watchbsp.h" +#include "commands.h" + + +class CPointfile; + +void Pointfile_Parse(CPointfile &pointfile); + + +class CPointfile : public ISAXHandler, public Renderable, public OpenGLRenderable { + enum { + MAX_POINTFILE = 8192, + }; + Vector3 s_pointvecs[MAX_POINTFILE]; + std::size_t s_num_points; + int m_displaylist; + static Shader *m_renderstate; + StringOutputStream m_characters; +public: + CPointfile() + { + } + + ~CPointfile() + { + } + + void Init(); + + void PushPoint(const Vector3 &v); + + void GenerateDisplayList(); + +// SAX interface + void Release() + { + // blank because not heap-allocated + } + + 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); + + const char *getName(); + + typedef const Vector3 *const_iterator; + + const_iterator begin() const + { + return &s_pointvecs[0]; + } + + const_iterator end() const + { + return &s_pointvecs[s_num_points]; + } + + bool shown() const + { + return m_displaylist != 0; + } + + void show(bool show) + { + if (show && !shown()) { + Pointfile_Parse(*this); + GenerateDisplayList(); + SceneChangeNotify(); + } else if (!show && shown()) { + glDeleteLists(m_displaylist, 1); + m_displaylist = 0; + SceneChangeNotify(); + } + } + + void render(RenderStateFlags state) const + { + glCallList(m_displaylist); + } + + void renderSolid(Renderer &renderer, const VolumeTest &volume) const + { + if (shown()) { + renderer.SetState(m_renderstate, Renderer::eWireframeOnly); + renderer.SetState(m_renderstate, Renderer::eFullMaterials); + renderer.addRenderable(*this, g_matrix4_identity); + } + } + + void renderWireframe(Renderer &renderer, const VolumeTest &volume) const + { + renderSolid(renderer, volume); + } + + static void constructStatic() + { + m_renderstate = GlobalShaderCache().capture("$POINTFILE"); + } + + static void destroyStatic() + { + GlobalShaderCache().release("$POINTFILE"); + } +}; + +Shader *CPointfile::m_renderstate = 0; + +namespace { + CPointfile s_pointfile; +} + +ISAXHandler &g_pointfile = s_pointfile; + +static CPointfile::const_iterator s_check_point; + +void CPointfile::Init() +{ + s_num_points = 0; + m_displaylist = 0; +} + +void CPointfile::PushPoint(const Vector3 &v) +{ + if (s_num_points < MAX_POINTFILE) { + s_pointvecs[s_num_points] = v; + ++s_num_points; + } +} + +// create the display list at the end +void CPointfile::GenerateDisplayList() +{ + m_displaylist = glGenLists(1); + + glNewList(m_displaylist, GL_COMPILE); + + glBegin(GL_LINE_STRIP); + for (std::size_t i = 0; i < s_num_points; i++) + glVertex3fv(vector3_to_array(s_pointvecs[i])); + glEnd(); + glLineWidth(1); + + glEndList(); +} + +// old (but still relevant) pointfile code ------------------------------------- + +void Pointfile_Delete(void) +{ + const char *mapname = Map_Name(g_map); + StringOutputStream name(256); + name << StringRange(mapname, path_get_filename_base_end(mapname)) << ".lin"; + file_remove(name.c_str()); +} + +// advance camera to next point +void Pointfile_Next(void) +{ + if (!s_pointfile.shown()) { + return; + } + + if (s_check_point + 2 == s_pointfile.end()) { + globalOutputStream() << "End 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); + } +} + +// 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", makeCallbackF(Pointfile_Toggle)); + GlobalCommands_insert("NextLeakSpot", makeCallbackF(Pointfile_Next), + Accelerator('K', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + GlobalCommands_insert("PrevLeakSpot", makeCallbackF(Pointfile_Prev), + 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 0000000..449af54 --- /dev/null +++ b/radiant/points.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 + */ + +//----------------------------------------------------------------------------- +// +// 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 0000000..08ca943 --- /dev/null +++ b/radiant/preferencedictionary.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 "preferencedictionary.h" diff --git a/radiant/preferencedictionary.h b/radiant/preferencedictionary.h new file mode 100644 index 0000000..7f826e8 --- /dev/null +++ b/radiant/preferencedictionary.h @@ -0,0 +1,280 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 { + Property m_cb; + public: + PreferenceEntry(const Property &cb) + : m_cb(cb) + { + } + + void importString(const char *string) + { + m_cb.set(string); + } + + void exportString(const Callback &importer) + { + m_cb.get(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 Property &cb) + { + m_preferences.insert(PreferenceEntries::value_type(name, PreferenceEntry(cb))); + PreferenceCache::iterator i = m_cache.find(name); + if (i != m_cache.end()) { + cb.set(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.erase(name); + m_cache.insert(PreferenceCache::value_type(name, value)); + } + } +}; + +inline void XMLPreference_importString(XMLImporter &importer, const char *value) +{ + importer.write(value, string_length(value)); +} + +typedef ReferenceCaller 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 0000000..d24051e --- /dev/null +++ b/radiant/preferences.cpp @@ -0,0 +1,1035 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 "globaldefs.h" + +#include +#include "gtkutil/image.h" +#include "environment.h" + +#include "debugging/debugging.h" + +#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) +{ +#if GDEF_OS_WINDOWS + page.appendCheckBox( "", "Default Text Editor", g_TextEditor_useWin32Editor ); +#else + { + ui::CheckButton use_custom = page.appendCheckBox("Text Editor", "Custom", g_TextEditor_useCustomEditor); + ui::Widget 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(makeCallbackF(Mouse_constructPage)); +} + + +/*! + ========================================================= + Games selection dialog + ========================================================= + */ + +#include +#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, const char *cmdline_prefix) +{ + bool ret = false; + TextFileInputStream file(filename); + if (!file.failed()) { + XMLStreamParser parser(file); + XMLPreferenceDictionaryImporter importer(preferences, PREFERENCES_VERSION); + parser.exportXML(importer); + ret = true; + } + + int l = strlen(cmdline_prefix); + for (int i = 1; i < g_argc - 1; ++i) { + if (g_argv[i][0] == '-') { + if (!strncmp(g_argv[i] + 1, cmdline_prefix, l)) { + if (g_argv[i][l + 1] == '-') { + preferences.importPref(g_argv[i] + l + 2, g_argv[i + 1]); + } + } + ++i; + } + } + + return ret; +} + +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); +} + + +struct LogConsole { + static void Export(const Callback &returnz) + { + returnz(g_Console_enableLogging); + } + + static void Import(bool value) + { + g_Console_enableLogging = value; + Sys_LogFile(g_Console_enableLogging); + } +}; + + +void RegisterGlobalPreferences(PreferenceSystem &preferences) +{ + preferences.registerPreference("gamefile", make_property_string(g_GamesDialog.m_sGameFile)); + preferences.registerPreference("gamePrompt", make_property_string(g_GamesDialog.m_bGamePrompt)); + preferences.registerPreference("log console", make_property_string()); +} + + +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(), "global")) { + 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; i < value; i++) { + ++iGame; + } + m_sGameFile = (*iGame)->mGameFile; +} + +void CGameDialog::GameFileExport(const Callback &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); +} + +struct CGameDialog_GameFile { + static void Export(const CGameDialog &self, const Callback &returnz) + { + self.GameFileExport(returnz); + } + + static void Import(CGameDialog &self, int value) + { + self.GameFileImport(value); + } +}; + +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())), + make_property(*this) + ); + page.appendCheckBox("Startup", "Show Global Preferences", m_bGamePrompt); +} + +ui::Window CGameDialog::BuildDialog() +{ + + auto frame = create_dialog_frame("Game settings", ui::Shadow::ETCHED_IN); + auto image = new_local_image("splash.xpm"); + image.show(); + auto vbox = ui::VBox(FALSE, 2); + auto vbox2 = create_dialog_vbox(0, 4); + vbox.show(); + vbox.pack_start(image, TRUE, TRUE, 0); + frame.add(vbox2); + vbox.pack_start(frame, FALSE, FALSE, 0); + + PreferencesPage preferencesPage(*this, vbox2); + Global_constructPreferences(preferencesPage); + CreateGlobalFrame(preferencesPage); + + return create_simple_modal_dialog_window("Global Preferences", m_modal, vbox); +} + +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, [&](const char *name) { + if (!extension_equal(path_get_extension(name), "game")) { + return; + } + StringOutputStream strPath(256); + strPath << path << 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"; + } + }); +} + +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"); + } else { + std::list::iterator iGame, iPrevGame; + for (iGame = mGames.begin(), iPrevGame = mGames.end(); iGame != mGames.end(); iPrevGame = iGame, ++iGame) { + if (iPrevGame != mGames.end()) { + if (strcmp((*iGame)->getRequiredKeyValue("name"), (*iPrevGame)->getRequiredKeyValue("name")) < 0) { + CGameDescription *h = *iGame; + *iGame = *iPrevGame; + *iPrevGame = h; + } + } + } + } + + 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()) { + Destroy(); + } +} + +inline const char *GameDescription_getIdentifier(const CGameDescription &gameDescription) +{ + const char *identifier = gameDescription.getKeyValue("index"); + if (string_empty(identifier)) { + identifier = "1"; + } + return identifier; +} + + +CGameDialog g_GamesDialog; + + +// ============================================================================= +// Widget callbacks for PrefsDlg + +static void OnButtonClean(ui::Widget widget, gpointer data) +{ + // make sure this is what the user wants + if (ui::alert(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", ui::alert_type::YESNO, ui::alert_icon::Asterisk) == ui::alert_response::YES) { + 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 + ======== + */ + +const char *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(ui::Widget notebook, ui::Widget 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(ui::Widget prefpage) +{ + notebook_set_page(m_notebook, prefpage); + return; +} + +static void treeSelection(ui::TreeSelection selection, gpointer data) +{ + PrefsDlg *dlg = (PrefsDlg *) data; + + GtkTreeModel *model; + GtkTreeIter selected; + if (gtk_tree_selection_get_selected(selection, &model, &selected)) { + ui::Widget prefpage{ui::null}; + 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(ui::Widget self, ui::Widget 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(ui::Widget toggleButton, ui::Widget self) +{ + Widget_updateDependency(self, toggleButton); +} + +void ToggleButton_state_changed_Widget_updateDependency(ui::Widget toggleButton, GtkStateType state, ui::Widget self) +{ + if (state == GTK_STATE_INSENSITIVE) { + Widget_updateDependency(self, toggleButton); + } +} + +void Widget_connectToggleDependency(ui::Widget self, ui::Widget toggleButton) +{ + toggleButton.connect("state_changed", G_CALLBACK(ToggleButton_state_changed_Widget_updateDependency), self); + toggleButton.connect("toggled", G_CALLBACK(ToggleButton_toggled_Widget_updateDependency), self); + Widget_updateDependency(self, toggleButton); +} + + +inline ui::VBox getVBox(ui::Bin page) +{ + return ui::VBox::from(gtk_bin_get_child(page)); +} + +GtkTreeIter PreferenceTree_appendPage(ui::TreeStore store, GtkTreeIter *parent, const char *name, ui::Widget page) +{ + GtkTreeIter group; + gtk_tree_store_append(store, &group, parent); + gtk_tree_store_set(store, &group, 0, name, 1, page, -1); + return group; +} + +ui::Bin PreferencePages_addPage(ui::Widget notebook, const char *name) +{ + ui::Widget preflabel = ui::Label(name); + preflabel.show(); + + auto pageframe = ui::Frame(name); + gtk_container_set_border_width(GTK_CONTAINER(pageframe), 4); + pageframe.show(); + + ui::Widget vbox = ui::VBox(FALSE, 4); + vbox.show(); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 4); + pageframe.add(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; + ui::Widget m_notebook; + ui::TreeStore m_store; + GtkTreeIter m_group; +public: + PreferenceTreeGroup(Dialog &dialog, ui::Widget notebook, ui::TreeStore store, GtkTreeIter group) : + m_dialog(dialog), + m_notebook(notebook), + m_store(store), + m_group(group) + { + } + + PreferencesPage createPage(const char *treeName, const char *frameName) + { + auto page = PreferencePages_addPage(m_notebook, frameName); + PreferenceTree_appendPage(m_store, &m_group, treeName, page); + return PreferencesPage(m_dialog, getVBox(page)); + } +}; + +ui::Window PrefsDlg::BuildDialog() +{ + PreferencesDialog_addInterfacePreferences(makeCallbackF(Interface_constructPreferences)); + Mouse_registerPreferencesPage(); + + ui::Window dialog = ui::Window(create_floating_window("WorldSpawn Preferences", m_parent)); + + { + auto mainvbox = ui::VBox(FALSE, 5); + dialog.add(mainvbox); + gtk_container_set_border_width(GTK_CONTAINER(mainvbox), 5); + mainvbox.show(); + + { + auto hbox = ui::HBox(FALSE, 5); + hbox.show(); + mainvbox.pack_end(hbox, FALSE, TRUE, 0); + + { + auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &m_modal); + hbox.pack_end(button, FALSE, FALSE, 0); + } + { + auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &m_modal); + hbox.pack_end(button, FALSE, FALSE, 0); + } + { + auto button = create_dialog_button("Clean", G_CALLBACK(OnButtonClean), this); + hbox.pack_end(button, FALSE, FALSE, 0); + } + } + + { + auto hbox = ui::HBox(FALSE, 5); + mainvbox.pack_start(hbox, TRUE, TRUE, 0); + hbox.show(); + + { + auto sc_win = ui::ScrolledWindow(ui::New); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sc_win), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + hbox.pack_start(sc_win, FALSE, FALSE, 0); + sc_win.show(); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sc_win), GTK_SHADOW_IN); + + // prefs pages notebook + m_notebook = ui::Widget::from(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); + hbox.pack_start(m_notebook, TRUE, TRUE, 0); + m_notebook.show(); + + + { + auto store = ui::TreeStore::from(gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER)); + + auto view = ui::TreeView(ui::TreeModel::from(store._handle)); + gtk_tree_view_set_headers_visible(view, FALSE); + + { + auto renderer = ui::CellRendererText(ui::New); + auto column = ui::TreeViewColumn("Preferences", renderer, {{"text", 0}}); + gtk_tree_view_append_column(view, column); + } + + { + auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view)); + selection.connect("changed", G_CALLBACK(treeSelection), this); + } + + view.show(); + + sc_win.add(view); + + { + /********************************************************************/ + /* Add preference tree options */ + /********************************************************************/ + // Front page... + //GtkWidget* front = + PreferencePages_addPage(m_notebook, "Front Page"); + + { + auto global = PreferencePages_addPage(m_notebook, "Global Preferences"); + { + PreferencesPage preferencesPage(*this, getVBox(global)); + Global_constructPreferences(preferencesPage); + } + auto group = PreferenceTree_appendPage(store, 0, "Global", global); + { + auto game = PreferencePages_addPage(m_notebook, "Game"); + PreferencesPage preferencesPage(*this, getVBox(game)); + g_GamesDialog.CreateGlobalFrame(preferencesPage); + + PreferenceTree_appendPage(store, &group, "Game", game); + } + } + + { + auto interfacePage = PreferencePages_addPage(m_notebook, "Interface Preferences"); + { + PreferencesPage preferencesPage(*this, getVBox(interfacePage)); + PreferencesPageCallbacks_constructPage(g_interfacePreferences, preferencesPage); + } + + auto group = PreferenceTree_appendPage(store, 0, "Interface", interfacePage); + PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group); + + PreferenceGroupCallbacks_constructGroup(g_interfaceCallbacks, preferenceGroup); + } + + { + auto display = PreferencePages_addPage(m_notebook, "Display Preferences"); + { + PreferencesPage preferencesPage(*this, getVBox(display)); + PreferencesPageCallbacks_constructPage(g_displayPreferences, preferencesPage); + } + auto group = PreferenceTree_appendPage(store, 0, "Display", display); + PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group); + + PreferenceGroupCallbacks_constructGroup(g_displayCallbacks, preferenceGroup); + } + + { + auto settings = PreferencePages_addPage(m_notebook, "General Settings"); + { + PreferencesPage preferencesPage(*this, getVBox(settings)); + PreferencesPageCallbacks_constructPage(g_settingsPreferences, preferencesPage); + } + + auto group = PreferenceTree_appendPage(store, 0, "Settings", settings); + PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group); + + PreferenceGroupCallbacks_constructGroup(g_settingsCallbacks, preferenceGroup); + } + } + + gtk_tree_view_expand_all(view); + + g_object_unref(G_OBJECT(store)); + } + } + } + } + + gtk_notebook_set_current_page(GTK_NOTEBOOK(m_notebook), 0); + + return dialog; +} + +preferences_globals_t g_preferences_globals; + +PrefsDlg g_Preferences; // global prefs instance + + +void PreferencesDialog_constructWindow(ui::Window 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, g_GamesDialog.m_sGameFile.c_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'; + } + ui::alert(MainFrame_getWindow(), message.c_str()); + g_restart_required.clear(); + } + } +} + +struct GameName { + static void Export(const Callback &returnz) + { + returnz(gamename_get()); + } + + static void Import(const char *value) + { + gamename_set(value); + } +}; + +struct GameMode { + static void Export(const Callback &returnz) + { + returnz(gamemode_get()); + } + + static void Import(const char *value) + { + gamemode_set(value); + } +}; + +void RegisterPreferences(PreferenceSystem &preferences) +{ +#if GDEF_OS_WINDOWS + preferences.registerPreference( "UseCustomShaderEditor", make_property_string( g_TextEditor_useWin32Editor ) ); +#else + preferences.registerPreference("UseCustomShaderEditor", make_property_string(g_TextEditor_useCustomEditor)); + preferences.registerPreference("CustomShaderEditorCommand", make_property_string(g_TextEditor_editorCommand)); +#endif + + preferences.registerPreference("GameName", make_property()); + preferences.registerPreference("GameMode", make_property()); +} + +void Preferences_Init() +{ + RegisterPreferences(GetPreferenceSystem()); +} diff --git a/radiant/preferences.h b/radiant/preferences.h new file mode 100644 index 0000000..a2e1b8e --- /dev/null +++ b/radiant/preferences.h @@ -0,0 +1,467 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 +#include "property.h" + +void Widget_connectToggleDependency(ui::Widget self, ui::Widget toggleButton); + +class PreferencesPage { + Dialog &m_dialog; + ui::VBox m_vbox; +public: + PreferencesPage(Dialog &dialog, ui::VBox vbox) : m_dialog(dialog), m_vbox(vbox) + { + } + + ui::CheckButton appendCheckBox(const char *name, const char *flag, bool &data) + { + return m_dialog.addCheckBox(m_vbox, name, flag, data); + } + + ui::CheckButton appendCheckBox(const char *name, const char *flag, Property const &cb) + { + return m_dialog.addCheckBox(m_vbox, name, flag, cb); + } + + void appendCombo(const char *name, StringArrayRange values, Property const &cb) + { + m_dialog.addCombo(m_vbox, name, values, cb); + } + + 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) + { + m_dialog.addSlider(m_vbox, name, data, draw_value, low, high, value, lower, upper, step_increment, + page_increment); + } + + void appendRadio(const char *name, StringArrayRange names, Property const &cb) + { + m_dialog.addRadio(m_vbox, name, names, cb); + } + + void appendRadio(const char *name, int &data, StringArrayRange names) + { + m_dialog.addRadio(m_vbox, name, data, names); + } + + void appendRadioIcons(const char *name, StringArrayRange icons, Property const &cb) + { + m_dialog.addRadioIcons(m_vbox, name, icons, cb); + } + + void appendRadioIcons(const char *name, int &data, StringArrayRange icons) + { + m_dialog.addRadioIcons(m_vbox, name, data, icons); + } + + ui::Widget appendEntry(const char *name, Property const &cb) + { + return m_dialog.addIntEntry(m_vbox, name, cb); + } + + ui::Widget appendEntry(const char *name, int &data) + { + return m_dialog.addEntry(m_vbox, name, data); + } + + ui::Widget appendEntry(const char *name, Property const &cb) + { + return m_dialog.addSizeEntry(m_vbox, name, cb); + } + + ui::Widget appendEntry(const char *name, std::size_t &data) + { + return m_dialog.addEntry(m_vbox, name, data); + } + + ui::Widget appendEntry(const char *name, Property const &cb) + { + return m_dialog.addFloatEntry(m_vbox, name, cb); + } + + ui::Widget appendEntry(const char *name, float &data) + { + return m_dialog.addEntry(m_vbox, name, data); + } + + ui::Widget appendPathEntry(const char *name, bool browse_directory, Property const &cb) + { + return m_dialog.addPathEntry(m_vbox, name, browse_directory, cb); + } + + ui::Widget appendPathEntry(const char *name, CopiedString &data, bool directory) + { + return m_dialog.addPathEntry(m_vbox, name, data, directory); + } + + ui::SpinButton appendSpinner(const char *name, int &data, double value, double lower, double upper) + { + return m_dialog.addSpinner(m_vbox, name, data, value, lower, upper); + } + + ui::SpinButton appendSpinner(const char *name, double value, double lower, double upper, Property const &cb) + { + return m_dialog.addSpinner(m_vbox, name, value, lower, upper, cb); + } + + ui::SpinButton appendSpinner(const char *name, double value, double lower, double upper, Property const &cb) + { + return m_dialog.addSpinner(m_vbox, name, value, lower, upper, cb); + } +}; + +typedef Callback PreferencesPageCallback; + +class PreferenceGroup { +public: + virtual PreferencesPage createPage(const char *treeName, const char *frameName) = 0; +}; + +typedef Callback 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; + } +}; + +template +struct PropertyImpl, T> { + static void Export(const LatchedValue &self, const Callback &returnz) + { + returnz(self.m_latched); + } + + static void Import(LatchedValue &self, T value) + { + self.m_latched = value; + if (value != self.m_value) { + PreferencesDialog_restartRequired(self.m_description); + } + } +}; + +template +Property make_property(LatchedValue &self) +{ + return make_property, T>(self); +} + +/*! + 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; + +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(); + +/*! + 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 + */ + ui::Window BuildDialog(); + + void GameFileImport(int value); + + void GameFileExport(const Callback &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: + + ui::Widget m_notebook{ui::null}; + + 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(ui::Widget prefpage); + +protected: + +/*! Dialog API */ + ui::Window 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; + +void PreferencesDialog_constructWindow(ui::Window 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 0000000..f4646f7 --- /dev/null +++ b/radiant/qe3.cpp @@ -0,0 +1,395 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 "globaldefs.h" + +#include + +#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 GDEF_OS_WINDOWS +#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(); + const char *userRoot = g_qeglobals.m_userEnginePath.c_str(); + const char *globalRoot = EnginePath_get(); + + // if we have a mod dir + if (!string_equal(gamename, basegame)) { + // ~/./ + if (userRoot && !g_disableHomePath) { + StringOutputStream userGamePath(256); + userGamePath << userRoot << gamename << '/'; + GlobalFileSystem().initDirectory(userGamePath.c_str()); + } + + // / + if (!g_disableEnginePath) { + StringOutputStream globalGamePath(256); + globalGamePath << globalRoot << gamename << '/'; + GlobalFileSystem().initDirectory(globalGamePath.c_str()); + } + } + + // ~/./ + if (userRoot && !g_disableHomePath) { + StringOutputStream userBasePath(256); + userBasePath << userRoot << basegame << '/'; + GlobalFileSystem().initDirectory(userBasePath.c_str()); + } + + // / + if (!g_disableEnginePath) { + StringOutputStream globalBasePath(256); + globalBasePath << globalRoot << basegame << '/'; + GlobalFileSystem().initDirectory(globalBasePath.c_str()); + } + + // extra pakpaths + for (int i = 0; i < g_pakPathCount; i++) { + if (g_strcmp0(g_strPakPath[i].c_str(), "")) { + GlobalFileSystem().initDirectory(g_strPakPath[i].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; + } + + auto result = ui::alert(MainFrame_getWindow(), + "The current map has changed since it was last saved.\nDo you want to save the current map before continuing?", + title, ui::alert_type::YESNOCANCEL, ui::alert_icon::Question); + if (result == ui::alert_response::CANCEL) { + return false; + } + if (result == ui::alert_response::YES) { + if (Map_Unnamed(g_map)) { + return Map_SaveAs(); + } else { + return Map_Save(); + } + } + return true; +} + +void bsp_init() +{ + build_set_variable("RadiantPath", AppPath_get()); + build_set_variable("ExecutableType", WorldSpawn_EXECUTABLE); + build_set_variable("EnginePath", EnginePath_get()); + build_set_variable("UserEnginePath", g_qeglobals.m_userEnginePath.c_str()); + build_set_variable("MonitorAddress", (g_WatchBSP_Enabled) ? "127.0.0.1:39000" : ""); + build_set_variable("GameName", gamename_get()); + + StringBuffer ExtraQ3map2Args; + // extra pakpaths + for (int i = 0; i < g_pakPathCount; i++) { + if (g_strcmp0(g_strPakPath[i].c_str(), "")) { + ExtraQ3map2Args.push_string(" -fs_pakpath \""); + ExtraQ3map2Args.push_string(g_strPakPath[i].c_str()); + ExtraQ3map2Args.push_string("\""); + } + } + + // extra switches + if (g_disableEnginePath) { + ExtraQ3map2Args.push_string(" -fs_nobasepath "); + } + + if (g_disableHomePath) { + ExtraQ3map2Args.push_string(" -fs_nohomepath "); + } + + build_set_variable("ExtraQ3map2Args", ExtraQ3map2Args.c_str()); + + const char *mapname = Map_Name(g_map); + StringOutputStream name(256); + name << StringRange(mapname, path_get_filename_base_end(mapname)) << ".bsp"; + + build_set_variable("MapFile", mapname); + build_set_variable("BspFile", name.c_str()); +} + +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 GDEF_OS_POSIX + strcpy(batpath, SettingsPath_get()); + strcat(batpath, "qe3bsp.sh"); +#elif GDEF_OS_WINDOWS + strcpy( batpath, SettingsPath_get() ); + strcat( batpath, "qe3bsp.bat" ); +#else +#error "unsupported platform" +#endif + bool written = false; + { + TextFileOutputStream batchFile(batpath); + if (!batchFile.failed()) { +#if GDEF_OS_POSIX + batchFile << "#!/bin/sh \n\n"; +#endif + BatchCommandListener listener(batchFile, junkpath); + build_run(name, listener); + written = true; + } + } + if (written) { +#if GDEF_OS_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, false); + } + } + + bsp_shutdown(); +} + +// ============================================================================= +// Sys_ functions + +void Sys_SetTitle(const char *text, bool modified) +{ + StringOutputStream title; + title << "WorldSpawn - ["; + title << text; + title << "]"; + + 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_get_window(MainFrame_getWindow()), cursor); + gdk_cursor_unref(cursor); + g_bWaitCursor = true; +} + +void Sys_EndWait(void) +{ + ScreenUpdates_Enable(); + gdk_window_set_cursor(gtk_widget_get_window(MainFrame_getWindow()), 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 0000000..c702a23 --- /dev/null +++ b/radiant/qe3.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 + */ + +#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 0000000..a1b1211 --- /dev/null +++ b/radiant/qgl.cpp @@ -0,0 +1,1516 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.h" + +#include "debugging/debugging.h" + +#include +#include +#include + +#if GDEF_OS_WINDOWS +#define WINGDIAPI __declspec( dllimport ) +#define APIENTRY __stdcall +#endif + +#if GDEF_OS_MACOS && !defined( XWINDOWS ) +#include +#else + +#include + +#endif + +#if GDEF_OS_WINDOWS +#undef WINGDIAPI +#undef APIENTRY +#endif + +#include "igl.h" + + +#if GDEF_OS_WINDOWS + +#include + +PROC ( WINAPI * qwglGetProcAddress )( LPCSTR ); + +#elif defined ( XWINDOWS ) + +#include +#include + +Bool ( *qglXQueryExtension )(Display *dpy, int *errorb, int *event); + +void *( *qglXGetProcAddressARB )(const GLubyte *procName); + +typedef void *( *glXGetProcAddressARBProc )(const GLubyte *procName); + +#elif GDEF_OS_MACOS +#include +#include +#include +#else +#error "unsupported platform" +#endif + + +void QGL_Shutdown(OpenGLBinding &table) +{ + globalOutputStream() << "Shutting down OpenGL module..."; + +#if GDEF_OS_WINDOWS + qwglGetProcAddress = 0; +#elif defined( XWINDOWS ) + qglXQueryExtension = glXQueryExtension; + qglXGetProcAddressARB = 0; +#elif GDEF_OS_MACOS +#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); +#if !GDEF_OS_MACOS + 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 GDEF_OS_MACOS + // Prepend a '_' for the Unix C symbol mangling convention + char *symbolName = (char *) malloc(strlen(symbol) + 2); + strcpy(symbolName + 1, symbol); + symbolName[0] = '_'; + NSSymbol nssymbol = NULL; + if (NSIsSymbolNameDefined(symbolName)) nssymbol = NSLookupAndBindSymbol(symbolName); + free(symbolName); + return nssymbol ? reinterpret_cast(NSAddressOfSymbol(nssymbol)) : reinterpret_cast(glInvalidFunction); +#elif GDEF_OS_WINDOWS + ASSERT_NOTNULL( qwglGetProcAddress ); + return (QGLFunctionPointer) 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 GDEF_OS_WINDOWS + qwglGetProcAddress = wglGetProcAddress; +#elif defined( XWINDOWS ) + qglXGetProcAddressARB = (glXGetProcAddressARBProc) dlsym(RTLD_DEFAULT, "glXGetProcAddressARB"); + if ((qglXQueryExtension == 0) || (qglXQueryExtension(XOpenDisplay(nullptr), 0, 0) != True)) { + return 0; + } +#elif GDEF_OS_MACOS +#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"; + g_maxTextureAnisotropy = 0; + } +} + +void QGL_sharedContextDestroyed(OpenGLBinding &table) +{ + QGL_clear(table); +} + + +void QGL_assertNoErrors(const char *file, int line) +{ + 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 at " << file << ":" << line << ": " << errorString); + } else { + ERROR_MESSAGE("OpenGL error at " << file << ":" << line << ": " << 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 0000000..9d80852 --- /dev/null +++ b/radiant/qgl.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_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/referencecache.cpp b/radiant/referencecache.cpp new file mode 100644 index 0000000..448cd91 --- /dev/null +++ b/radiant/referencecache.cpp @@ -0,0 +1,812 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.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 GDEF_OS_WINDOWS + 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(makeCallbackF(MapChanged)); + } + } + + 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 0000000..92d5dc8 --- /dev/null +++ b/radiant/referencecache.h @@ -0,0 +1,46 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..76e80f4 --- /dev/null +++ b/radiant/renderer.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 "renderer.h" diff --git a/radiant/renderer.h b/radiant/renderer.h new file mode 100644 index 0000000..e5035c0 --- /dev/null +++ b/radiant/renderer.h @@ -0,0 +1,188 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 >(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 ConstMemberCaller 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 0000000..ead3cef --- /dev/null +++ b/radiant/renderstate.cpp @@ -0,0 +1,2522 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 ¤t, 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: + using func = void(const RendererLight &); + + 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: + using func = void(RendererLight &); + + 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(makeCallback(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); +} + +void ShaderCache_Destroy() +{ + GlobalShaderSystem().detach(*g_ShaderCache); + GlobalTexturesCache().detach(*g_ShaderCache); + delete g_ShaderCache; +} + +ShaderCache *GetShaderCache() +{ + return g_ShaderCache; +} + +inline void setTextureState(GLint ¤t, 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 ¤t, 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 ¤t, 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); + glEnable(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); + glDisable(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_RESCALE_NORMAL); // 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_POLYOFS) { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-0.05, -25 * self.m_polygonoffset); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } + + 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; + + 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 ¤t, 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 ¤t, 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.5f; + 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_POLYOFS) != 0) { + state.m_polygonoffset = m_shader->getPolygonOffset(); + state.m_state |= RENDER_POLYOFS; + } + + 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; + break; + case IShader::eEqual: + state.m_alphafunc = GL_EQUAL; + break; + case IShader::eLess: + state.m_alphafunc = GL_LESS; + break; + case IShader::eGreater: + state.m_alphafunc = GL_GREATER; + break; + case IShader::eLEqual: + state.m_alphafunc = GL_LEQUAL; + break; + case IShader::eGEqual: + state.m_alphafunc = GL_GEQUAL; + break; + } + } + 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 0000000..f4ba496 --- /dev/null +++ b/radiant/renderstate.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_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 0000000..506ba8a --- /dev/null +++ b/radiant/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by worldspawn.rc +// +#define IDI_WORLDSPAWN 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 0000000..9fd58c1 --- /dev/null +++ b/radiant/scenegraph.cpp @@ -0,0 +1,296 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" + +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, [&](const char *other) { + return string_equal(name, other); + }); + 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 0000000..48fb08b --- /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 0000000..8798f61 --- /dev/null +++ b/radiant/select.cpp @@ -0,0 +1,1245 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 + +#include "debugging/debugging.h" + +#include "ientity.h" +#include "eclasslib.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" +#include "entityinspector.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); + } + } +}; + + + +class DeleteEmpty : public scene::Graph::Walker { + mutable bool m_remove; + public: + DeleteEmpty() + : m_remove(false) + { + } + + bool pre(const scene::Path &path, scene::Instance &instance) const + { + + return true; + } + + void post(const scene::Path &path, scene::Instance &instance) const + { + /* skip point ents, crash otherwise */ + Entity *entity = Node_getEntity(path.top()); + if (entity != 0 && entity->getEntityClass().fixedsize) { + return; + } + + // delete empty entities + 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_DeleteEmpty() +{ + GlobalSceneGraph().traverse(DeleteEmpty()); + SceneChangeNotify(); +} + +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; + NodeSmartReference worldspawn; +public: + ExpandSelectionToEntitiesWalker() : m_depth(0), worldspawn(Map_FindOrInsertWorldspawn(g_map)) + { + } + + bool pre(const scene::Path &path, scene::Instance &instance) const + { + ++m_depth; + + // ignore worldspawn + NodeSmartReference me(path.top().get()); + if (me == worldspawn) { + return false; + } + + 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.isSelected(); + } 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, bool ignorebasis) +{ + if (GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) { + Scene_BrushSetTexdef_Selected(GlobalSceneGraph(), projection, ignorebasis); + } + Scene_BrushSetTexdef_Component_Selected(GlobalSceneGraph(), projection, ignorebasis); +} + +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 PropertyValues; + +bool propertyvalues_contain(const PropertyValues &propertyvalues, const char *str) +{ + for (PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i) { + if (string_equal(str, *i)) { + return true; + } + } + return false; +} + +class EntityFindByPropertyValueWalker : public scene::Graph::Walker { + const PropertyValues &m_propertyvalues; + const char *m_prop; +public: + EntityFindByPropertyValueWalker(const char *prop, const PropertyValues &propertyvalues) + : m_propertyvalues(propertyvalues), m_prop(prop) + { + } + + bool pre(const scene::Path &path, scene::Instance &instance) const + { + Entity *entity = Node_getEntity(path.top()); + if (entity != 0 + && propertyvalues_contain(m_propertyvalues, entity->getKeyValue(m_prop))) { + Instance_getSelectable(instance)->setSelected(true); + } + return true; + } +}; + +void Scene_EntitySelectByPropertyValues(scene::Graph &graph, const char *prop, const PropertyValues &propertyvalues) +{ + graph.traverse(EntityFindByPropertyValueWalker(prop, propertyvalues)); +} + +class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker { + PropertyValues &m_propertyvalues; + const char *m_prop; +public: + EntityGetSelectedPropertyValuesWalker(const char *prop, PropertyValues &propertyvalues) + : m_propertyvalues(propertyvalues), m_prop(prop) + { + } + + 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) { + if (!propertyvalues_contain(m_propertyvalues, entity->getKeyValue(m_prop))) { + m_propertyvalues.push_back(entity->getKeyValue(m_prop)); + } + } + } + return true; + } +}; + +void Scene_EntityGetPropertyValues(scene::Graph &graph, const char *prop, PropertyValues &propertyvalues) +{ + graph.traverse(EntityGetSelectedPropertyValuesWalker(prop, propertyvalues)); +} + +void Select_AllOfType() +{ + if (GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) { + if (GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace) { + GlobalSelectionSystem().setSelectedAllComponents(false); + Scene_BrushSelectByShader_Component(GlobalSceneGraph(), + TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); + } + } else { + PropertyValues propertyvalues; + const char *prop = EntityInspector_getCurrentKey(); + if (!prop || !*prop) { + prop = "classname"; + } + Scene_EntityGetPropertyValues(GlobalSceneGraph(), prop, propertyvalues); + GlobalSelectionSystem().setSelectedAll(false); + if (!propertyvalues.empty()) { + Scene_EntitySelectByPropertyValues(GlobalSceneGraph(), prop, propertyvalues); + } 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); +} + +void HideUnselected() +{ + Select_Invert(); + 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 FreeCaller SceneSelectionChangeCaller; + GlobalSelectionSystem().addSelectionChangeCallback(SceneSelectionChangeCaller()); + typedef FreeCaller UpdateWorkzoneForSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(UpdateWorkzoneForSelectionChangedCaller()); + typedef FreeCaller UpdateWorkzoneForSelectionCaller; + Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback(UpdateWorkzoneForSelectionCaller()); +} + +void Selection_destroy() +{ + GlobalSceneGraph().removeBoundsChangedCallback(Selection_boundsChanged); +} + + +#include "gtkdlgs.h" +#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 { + ui::SpinButton x{ui::null}; + ui::SpinButton y{ui::null}; + ui::SpinButton z{ui::null}; + ui::Window window{ui::null}; +}; + +static gboolean rotatedlg_apply(ui::Widget widget, RotateDialog *rotateDialog) +{ + Vector3 eulerXYZ; + + eulerXYZ[0] = static_cast( gtk_spin_button_get_value(rotateDialog->x)); + eulerXYZ[1] = static_cast( gtk_spin_button_get_value(rotateDialog->y)); + eulerXYZ[2] = static_cast( gtk_spin_button_get_value(rotateDialog->z)); + + 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)); + return TRUE; +} + +static gboolean rotatedlg_cancel(ui::Widget widget, RotateDialog *rotateDialog) +{ + rotateDialog->window.hide(); + + gtk_spin_button_set_value(rotateDialog->x, 0.0f); // reset to 0 on close + gtk_spin_button_set_value(rotateDialog->y, 0.0f); + gtk_spin_button_set_value(rotateDialog->z, 0.0f); + + return TRUE; +} + +static gboolean rotatedlg_ok(ui::Widget widget, RotateDialog *rotateDialog) +{ + rotatedlg_apply(widget, rotateDialog); + rotateDialog->window.hide(); + return TRUE; +} + +static gboolean rotatedlg_delete(ui::Widget widget, GdkEventAny *event, RotateDialog *rotateDialog) +{ + rotatedlg_cancel(widget, rotateDialog); + return TRUE; +} + +RotateDialog g_rotate_dialog; + +void DoRotateDlg() +{ + if (!g_rotate_dialog.window) { + g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", + G_CALLBACK(rotatedlg_delete), + &g_rotate_dialog); + + auto accel = ui::AccelGroup(ui::New); + g_rotate_dialog.window.add_accel_group(accel); + + { + auto hbox = create_dialog_hbox(4, 4); + g_rotate_dialog.window.add(hbox); + { + auto table = create_dialog_table(3, 2, 4, 4); + hbox.pack_start(table, TRUE, TRUE, 0); + { + ui::Widget label = ui::Label(" X "); + label.show(); + table.attach(label, {0, 1, 0, 1}, {0, 0}); + } + { + ui::Widget label = ui::Label(" Y "); + label.show(); + table.attach(label, {0, 1, 1, 2}, {0, 0}); + } + { + ui::Widget label = ui::Label(" Z "); + label.show(); + table.attach(label, {0, 1, 2, 3}, {0, 0}); + } + { + auto adj = ui::Adjustment(0, -359, 359, 1, 10, 0); + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + spin.dimensions(64, -1); + gtk_spin_button_set_wrap(spin, TRUE); + + gtk_widget_grab_focus(spin); + + g_rotate_dialog.x = spin; + } + { + auto adj = ui::Adjustment(0, -359, 359, 1, 10, 0); + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + spin.dimensions(64, -1); + gtk_spin_button_set_wrap(spin, TRUE); + + g_rotate_dialog.y = spin; + } + { + auto adj = ui::Adjustment(0, -359, 359, 1, 10, 0); + auto spin = ui::SpinButton(adj, 1, 0); + spin.show(); + table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0}); + spin.dimensions(64, -1); + gtk_spin_button_set_wrap(spin, TRUE); + + g_rotate_dialog.z = spin; + } + } + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + auto button = create_dialog_button("OK", G_CALLBACK(rotatedlg_ok), &g_rotate_dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_dialog_button("Cancel", G_CALLBACK(rotatedlg_cancel), &g_rotate_dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_dialog_button("Apply", G_CALLBACK(rotatedlg_apply), &g_rotate_dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + } + } + } + } + + g_rotate_dialog.window.show(); +} + + +struct ScaleDialog { + ui::Entry x{ui::null}; + ui::Entry y{ui::null}; + ui::Entry z{ui::null}; + ui::Window window{ui::null}; +}; + +static gboolean scaledlg_apply(ui::Widget widget, ScaleDialog *scaleDialog) +{ + float sx, sy, sz; + + sx = static_cast( atof(gtk_entry_get_text(GTK_ENTRY(scaleDialog->x)))); + sy = static_cast( atof(gtk_entry_get_text(GTK_ENTRY(scaleDialog->y)))); + sz = static_cast( atof(gtk_entry_get_text(GTK_ENTRY(scaleDialog->z)))); + + StringOutputStream command; + command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz; + UndoableCommand undo(command.c_str()); + + Select_Scale(sx, sy, sz); + + return TRUE; +} + +static gboolean scaledlg_cancel(ui::Widget widget, ScaleDialog *scaleDialog) +{ + scaleDialog->window.hide(); + + scaleDialog->x.text("1.0"); + scaleDialog->y.text("1.0"); + scaleDialog->z.text("1.0"); + + return TRUE; +} + +static gboolean scaledlg_ok(ui::Widget widget, ScaleDialog *scaleDialog) +{ + scaledlg_apply(widget, scaleDialog); + scaleDialog->window.hide(); + return TRUE; +} + +static gboolean scaledlg_delete(ui::Widget widget, GdkEventAny *event, ScaleDialog *scaleDialog) +{ + scaledlg_cancel(widget, scaleDialog); + return TRUE; +} + +ScaleDialog g_scale_dialog; + +void DoScaleDlg() +{ + if (!g_scale_dialog.window) { + g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", + G_CALLBACK(scaledlg_delete), + &g_scale_dialog); + + auto accel = ui::AccelGroup(ui::New); + g_scale_dialog.window.add_accel_group(accel); + + { + auto hbox = create_dialog_hbox(4, 4); + g_scale_dialog.window.add(hbox); + { + auto table = create_dialog_table(3, 2, 4, 4); + hbox.pack_start(table, TRUE, TRUE, 0); + { + ui::Widget label = ui::Label(" X "); + label.show(); + table.attach(label, {0, 1, 0, 1}, {0, 0}); + } + { + ui::Widget label = ui::Label(" Y "); + label.show(); + table.attach(label, {0, 1, 1, 2}, {0, 0}); + } + { + ui::Widget label = ui::Label(" Z "); + label.show(); + table.attach(label, {0, 1, 2, 3}, {0, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.text("1.0"); + entry.show(); + table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + + g_scale_dialog.x = entry; + } + { + auto entry = ui::Entry(ui::New); + entry.text("1.0"); + entry.show(); + table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + + g_scale_dialog.y = entry; + } + { + auto entry = ui::Entry(ui::New); + entry.text("1.0"); + entry.show(); + table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0}); + + g_scale_dialog.z = entry; + } + } + { + auto vbox = create_dialog_vbox(4); + hbox.pack_start(vbox, TRUE, TRUE, 0); + { + auto button = create_dialog_button("OK", G_CALLBACK(scaledlg_ok), &g_scale_dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + widget_make_default(button); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_dialog_button("Cancel", G_CALLBACK(scaledlg_cancel), &g_scale_dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0, + (GtkAccelFlags) 0); + } + { + auto button = create_dialog_button("Apply", G_CALLBACK(scaledlg_apply), &g_scale_dialog); + vbox.pack_start(button, FALSE, FALSE, 0); + } + } + } + } + + g_scale_dialog.window.show(); +} diff --git a/radiant/select.h b/radiant/select.h new file mode 100644 index 0000000..4a8b3e7 --- /dev/null +++ b/radiant/select.h @@ -0,0 +1,111 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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, bool ignorebasis); + +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 HideUnselected(); + +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 0000000..a8666cc --- /dev/null +++ b/radiant/selection.cpp @@ -0,0 +1,4092 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.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" + +TextOutputStream &ostream_write(TextOutputStream &t, const Vector4 &v) +{ + return t << "[ " << v.x() << " " << v.y() << " " << v.z() << " " << v.w() << " ]"; +} + +TextOutputStream &ostream_write(TextOutputStream &t, const Matrix4 &m) +{ + return t << "[ " << m.x() << " " << m.y() << " " << m.z() << " " << m.t() << " ]"; +} + +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 GDEF_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 ~Rotatable() = default; + + 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 ~Translatable() = default; + + 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, GetSnapGridSize()); + + 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, GetSnapGridSize()); + + m_translatable.translate(current); + } +}; + + +class Scalable { +public: + virtual ~Scalable() = default; + + 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, GetSnapGridSize()); + + Vector3 start(vector3_snapped(m_start, GetSnapGridSize())); + 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, GetSnapGridSize()); + + Vector3 start(vector3_snapped(m_start, GetSnapGridSize())); + 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 < m_primitives.size(); ++i) { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_primitives[i].m_points[0].colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_primitives[i].m_points[0].vertex); + switch (m_primitives[i].m_count) { + case 1: + break; + case 2: + glDrawArrays(GL_LINES, 0, GLsizei(m_primitives[i].m_count)); + break; + default: + glDrawArrays(GL_POLYGON, 0, GLsizei(m_primitives[i].m_count)); + break; + } + } + } + + void construct(const Matrix4 &world2device) + { + m_inverse = matrix4_full_inverse(world2device); + m_world = g_matrix4_identity; + } + + void insert(const Vector4 clipped[9], std::size_t count) + { + add_one(); + + m_primitives.back().m_count = count; + for (std::size_t i = 0; i < count; ++i) { + Vector3 world_point(vector4_projected(matrix4_transformed_vector4(m_inverse, clipped[i]))); + m_primitives.back().m_points[i].vertex = vertex3f_for_vector3(world_point); + } + } + + void destroy() + { + m_primitives.clear(); + } + +private: + void add_one() + { + m_primitives.push_back(primitive_t()); + + const Colour4b colour_clipped(255, 127, 0, 255); + + for (std::size_t i = 0; i < 9; ++i) { + m_primitives.back().m_points[i].colour = colour_clipped; + } + } +}; + +#if GDEF_DEBUG +//#define DEBUG_SELECTION +#endif + +#if defined( DEBUG_SELECTION ) +Shader *g_state_clipped; +RenderableClippedPrimitive g_render_clipped; +#endif + + +#if 0 + // dist_Point_to_Line(): get the distance of a point to a line. +// Input: a Point P and a Line L (in any dimension) +// Return: the shortest distance from P to L +float +dist_Point_to_Line( Point P, Line L ){ + Vector v = L.P1 - L.P0; + Vector w = P - L.P0; + + double c1 = dot( w,v ); + double c2 = dot( v,v ); + double b = c1 / c2; + + Point Pb = L.P0 + b * v; + return d( P, Pb ); +} +#endif + +class Segment3D { + typedef Vector3 point_type; +public: + Segment3D(const point_type &_p0, const point_type &_p1) + : p0(_p0), p1(_p1) + { + } + + point_type p0, p1; +}; + +typedef Vector3 Point3D; + +inline double vector3_distance_squared(const Point3D &a, const Point3D &b) +{ + return vector3_length_squared(b - a); +} + +// get the distance of a point to a segment. +Point3D segment_closest_point_to_point(const Segment3D &segment, const Point3D &point) +{ + Vector3 v = segment.p1 - segment.p0; + Vector3 w = point - segment.p0; + + double c1 = vector3_dot(w, v); + if (c1 <= 0) { + return segment.p0; + } + + double c2 = vector3_dot(v, v); + if (c2 <= c1) { + return segment.p1; + } + + return Point3D(segment.p0 + v * (c1 / c2)); +} + +double segment_dist_to_point_3d(const Segment3D &segment, const Point3D &point) +{ + return vector3_distance_squared(point, segment_closest_point_to_point(segment, point)); +} + +typedef Vector3 point_t; +typedef const Vector3 *point_iterator_t; + +// crossing number test for a point in a polygon +// This code is patterned after [Franklin, 2000] +bool point_test_polygon_2d(const point_t &P, point_iterator_t start, point_iterator_t finish) +{ + std::size_t crossings = 0; + + // loop through all edges of the polygon + for (point_iterator_t prev = finish - 1, cur = start; + cur != finish; prev = cur, ++cur) { // edge from (*prev) to (*cur) + if ((((*prev)[1] <= P[1]) && ((*cur)[1] > 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 < count; ++i) { + normalised[i][0] = clipped[i][0] / clipped[i][3]; + normalised[i][1] = clipped[i][1] / clipped[i][3]; + normalised[i][2] = clipped[i][2] / clipped[i][3]; + } + } + + if (cull != eClipCullNone && count > 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 < size; ++i) { + const std::size_t count = matrix4_clip_triangle(local2view, g_vector3_identity, + vertex3f_to_vector3(vertices[i].vertex), + vertex3f_to_vector3(vertices[(i + 1) % size].vertex), clipped); + BestPoint(count, clipped, best, cull); + } +} + +void +Quad_BestPoint(const Matrix4 &local2view, clipcull_t cull, const PointVertex *vertices, SelectionIntersection &best) +{ + Vector4 clipped[9]; + { + const std::size_t count = matrix4_clip_triangle(local2view, vertex3f_to_vector3(vertices[0].vertex), + vertex3f_to_vector3(vertices[1].vertex), + vertex3f_to_vector3(vertices[3].vertex), clipped); + BestPoint(count, clipped, best, cull); + } + { + const std::size_t count = matrix4_clip_triangle(local2view, vertex3f_to_vector3(vertices[1].vertex), + vertex3f_to_vector3(vertices[2].vertex), + vertex3f_to_vector3(vertices[3].vertex), clipped); + BestPoint(count, clipped, best, cull); + } +} + +struct FlatShadedVertex { + Vertex3f vertex; + Colour4b colour; + Normal3f normal; + + FlatShadedVertex() + { + } +}; + + +typedef FlatShadedVertex *FlatShadedVertexIterator; + +void Triangles_BestPoint(const Matrix4 &local2view, clipcull_t cull, FlatShadedVertexIterator first, + FlatShadedVertexIterator last, SelectionIntersection &best) +{ + for (FlatShadedVertexIterator x(first), y(first + 1), z(first + 2); x != last; x += 3, y += 3, z += 3) { + Vector4 clipped[9]; + BestPoint( + matrix4_clip_triangle( + local2view, + reinterpret_cast((*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 MemberCaller 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 < count; ++i) { + BestPoint( + matrix4_clip_triangle( + m_local2view, + reinterpret_cast( 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: + using func = void(const Selectable &); + + 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_matrix_transform(Vector3 &parent_translation, const Matrix4 &local_transform, + const Vector3 &world_pivot, const Matrix4 &localToWorld, + const Matrix4 &localToParent) +{ + // we need a translation inside the parent system to move the origin of this object to the right place + + // mathematically, it must fulfill: + // + // local_translation local_transform local_pivot = local_pivot + // local_translation = local_pivot - local_transform local_pivot + // + // or maybe? + // local_transform local_translation local_pivot = local_pivot + // local_translation local_pivot = local_transform^-1 local_pivot + // local_translation + local_pivot = local_transform^-1 local_pivot + // local_translation = local_transform^-1 local_pivot - local_pivot + + Vector3 local_pivot(get_local_pivot(world_pivot, localToWorld)); + + Vector3 local_translation( + vector3_subtracted( + local_pivot, + matrix4_transformed_point( + local_transform, + local_pivot + ) + /* + matrix4_transformed_point( + matrix4_full_inverse(local_transform), + local_pivot + ), + local_pivot + */ + ) + ); + + translation_local2object(parent_translation, local_translation, localToParent); + + /* + // verify it! + globalOutputStream() << "World pivot is at " << world_pivot << "\n"; + globalOutputStream() << "Local pivot is at " << local_pivot << "\n"; + globalOutputStream() << "Transformation " << local_transform << " moves it to: " << matrix4_transformed_point(local_transform, local_pivot) << "\n"; + globalOutputStream() << "Must move by " << local_translation << " in the local system" << "\n"; + globalOutputStream() << "Must move by " << parent_translation << " in the parent system" << "\n"; + */ +} + +void translation_for_pivoted_rotation(Vector3 &parent_translation, const Quaternion &local_rotation, + const Vector3 &world_pivot, const Matrix4 &localToWorld, + const Matrix4 &localToParent) +{ + translation_for_pivoted_matrix_transform(parent_translation, + matrix4_rotation_for_quaternion_quantised(local_rotation), world_pivot, + localToWorld, localToParent); +} + +void translation_for_pivoted_scale(Vector3 &parent_translation, const Vector3 &world_scale, const Vector3 &world_pivot, + const Matrix4 &localToWorld, const Matrix4 &localToParent) +{ + Matrix4 local_transform( + matrix4_multiplied_by_matrix4( + matrix4_full_inverse(localToWorld), + matrix4_multiplied_by_matrix4( + matrix4_scale_for_vec3(world_scale), + localToWorld + ) + ) + ); + local_transform.tx() = local_transform.ty() = local_transform.tz() = 0; // cancel translation parts + translation_for_pivoted_matrix_transform(parent_translation, local_transform, world_pivot, localToWorld, + 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)); +} +extern bool g_expansion_enabled; + +void Scene_ExpandSelectionToEntities(); + +// 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 MemberCaller 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 makeCallback(m_count_primitive); + } else { + return makeCallback(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 MemberCaller 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); + + if (modifier == eToggle && g_expansion_enabled == true) { /* eukara: hack? */ + Scene_ExpandSelectionToEntities(); + } + } + } + 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, GetSnapGridSize()); + 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 Callback MouseEventCallback; + +Single g_mouseMovedCallback; +Single g_mouseUpCallback; + + +const ButtonIdentifier c_button_select = c_buttonLeft; +const ButtonIdentifier c_button_texture = c_buttonMiddle; +const ModifierFlags c_modifier_manipulator = c_modifierNone; +const ModifierFlags c_modifier_toggle = c_modifierShift; +const ModifierFlags c_modifier_toggle_face = c_modifierShift | c_modifierControl; +const ModifierFlags c_modifier_face = c_modifierControl; +const ModifierFlags c_modifier_replace = c_modifierShift | c_modifierAlt; +const ModifierFlags c_modifier_replace_face = c_modifier_replace | c_modifier_face; +const ModifierFlags c_modifier_apply_texture1 = c_modifierControl | c_modifierShift; +const ModifierFlags c_modifier_apply_texture2 = c_modifierControl; +const ModifierFlags c_modifier_apply_texture3 = 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 MemberCaller MouseMovedCaller; + + void mouseUp(DeviceVector position) + { + testSelect(device_constrained(position)); + + g_mouseMovedCallback.clear(); + g_mouseUpCallback.clear(); + } + + typedef MemberCaller 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 MemberCaller MouseMovedCaller; + + void mouseUp(DeviceVector position) + { + getSelectionSystem().endMove(); + g_mouseMovedCallback.clear(); + g_mouseUpCallback.clear(); + } + + typedef MemberCaller 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_texture1 || modifiers == c_modifier_apply_texture2 || + modifiers == c_modifier_apply_texture3) { + 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 0000000..b15ff93 --- /dev/null +++ b/radiant/selection.h @@ -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 + */ + +#if !defined( INCLUDED_SELECTION_H ) +#define INCLUDED_SELECTION_H + +#include "windowobserver.h" +#include "generic/callback.h" + +struct rect_t { + float min[2]; + float max[2]; +}; + +typedef Callback RectangleCallback; + +class View; + +class SelectionSystemWindowObserver : public WindowObserver { +public: + virtual ~SelectionSystemWindowObserver() = default; + + 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 0000000..718a0a2 --- /dev/null +++ b/radiant/server.cpp @@ -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 + */ + +#include "server.h" +#include "globaldefs.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) + { + 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 GDEF_OS_WINDOWS + +#include + +const int 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 = (FunctionPointer) GetProcAddress( m_library, symbol ); + if ( address == 0 ) { + globalErrorStream() << "GetProcAddress failed: '" << symbol << "'\n"; + globalErrorStream() << "GetLastError: " << FormatGetLastError(); + } + return address; +} +}; + +#elif GDEF_OS_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_DLLIMPORT *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")); +#if 0 + if ( !m_registerModule ) { + m_registerModule = reinterpret_cast( m_library.findSymbol( "Radiant_RegisterModules@4" ) ); + } +#endif + } + } + + 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 0000000..c54b9d2 --- /dev/null +++ b/radiant/server.h @@ -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 + */ + +#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 0000000..aec784a --- /dev/null +++ b/radiant/shaders.cpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#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 0000000..7c6bd2b --- /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 0000000..cf5d2dd --- /dev/null +++ b/radiant/sockets.cpp @@ -0,0 +1,47 @@ +#include "sockets.h" +#include "globaldefs.h" + +#if GDEF_OS_WINDOWS +#include +#elif GDEF_OS_POSIX + +#include + +const int SOCKET_ERROR = -1; +#else +#error "unsupported platform" +#endif + +#if GDEF_OS_MACOS +#include +#endif + +int Net_Wait(socket_t *sock, long sec, long usec) +{ +// used for select() +#if GDEF_OS_WINDOWS + TIMEVAL tout = { sec, usec }; +#endif +#if GDEF_OS_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 0000000..2ab6264 --- /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 0000000..e2d383f --- /dev/null +++ b/radiant/stacktrace.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 "stacktrace.h" +#include "globaldefs.h" + +#include "stream/textstream.h" + +#include "environment.h" + +#if GDEF_OS_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); + } +} + +#elif GDEF_COMPILER_MSVC + +#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 ) { + } +} + +#elif GDEF_OS_WINDOWS +void write_stack_trace( TextOutputStream& outputStream ){ + outputStream << "\nStacktrace is disabled on this compiler\n"; +} +#else +void write_stack_trace( TextOutputStream& outputStream ){ + outputStream << "\nStacktrace is disabled on this platform\n"; +} +#endif diff --git a/radiant/stacktrace.h b/radiant/stacktrace.h new file mode 100644 index 0000000..8475ceb --- /dev/null +++ b/radiant/stacktrace.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_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 0000000..e3b2299 --- /dev/null +++ b/radiant/surfacedialog.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 + */ + +// +// Surface Dialog +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "surfacedialog.h" + +#include + +#include "debugging/debugging.h" +#include "warnings.h" + +#include "iscenegraph.h" +#include "itexdef.h" +#include "iundo.h" +#include "iselection.h" + +#include + +#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( ui::Widget, GtkAllocation *, gpointer ); +gboolean expose( ui::Widget, GdkEventExpose *, gpointer ); +gboolean button_press( ui::Widget, GdkEventButton *, gpointer ); +gboolean button_release( ui::Widget, GdkEventButton *, gpointer ); +gboolean motion( ui::Widget, GdkEventMotion *, gpointer ); +void flipX( ui::ToggleButton, gpointer ); +void flipY( ui::ToggleButton, gpointer ); + +//End Textool function prototypes + +//Shamus: Textool globals +ui::Widget g_textoolWin; +//End Textool globals + +void queueDraw(){ + gtk_widget_queue_draw( g_textoolWin ); +} + +} + +#endif + +inline void spin_button_set_step(ui::SpinButton spin, gfloat step) +{ +#if 1 + gtk_adjustment_set_step_increment(gtk_spin_button_get_adjustment(spin), 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: + ui::SpinButton m_spin; + ui::Entry m_entry; + + Increment(float &f) : m_f(f), m_spin(ui::null), m_entry(ui::null) + { + } + + 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 { + ui::Window 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; + ui::Entry m_valueEntryWidget{ui::null}; +public: + WindowPositionTracker m_positionTracker; + +// Dialog Data + float m_fitHorizontal; + float m_fitVertical; + + Increment m_hshiftIncrement; + Increment m_vshiftIncrement; + Increment m_hscaleIncrement; + Increment m_vscaleIncrement; + Increment m_rotateIncrement; + ui::Entry m_texture{ui::null}; + + 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_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(ui::Window main_window) + { + m_parent = main_window; + Create(); + AddGridChangeCallback(FreeCaller()); + } + + void destroyWindow() + { + Destroy(); + } + + bool visible() + { + return GetWidget().visible(); + } + + 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(ui::Window 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(ui::Widget widget, gpointer data) +{ + float hscale, vscale; + hscale = static_cast( gtk_spin_button_get_value(getSurfaceInspector().m_hscaleIncrement.m_spin)); + vscale = static_cast( gtk_spin_button_get_value(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()) { + 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(ui::Widget widget, gpointer data) +{ + Patch_CapTexture(); +} + +static void OnBtnPatchnatural(ui::Widget widget, gpointer data) +{ + Patch_NaturalTexture(); +} + +static void OnBtnPatchreset(ui::Widget widget, gpointer data) +{ + Patch_ResetTexture(); +} + +static void OnBtnPatchFit(ui::Widget widget, gpointer data) +{ + Patch_FitTexture(); +} + +static void OnBtnAxial(ui::Widget 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, true); +} + +static void OnBtnFaceFit(ui::Widget 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(ui::ToggleButton button, const Callback &callback) +{ + return g_signal_connect_swapped(G_OBJECT(button), "toggled", G_CALLBACK(callback.getThunk()), + callback.getEnvironment()); +} + +ui::Window SurfaceInspector::BuildDialog() +{ + ui::Window window = ui::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: + auto vbox = ui::VBox(FALSE, 5); + vbox.show(); + window.add(vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); + + { + auto hbox2 = ui::HBox(FALSE, 5); + hbox2.show(); + vbox.pack_start(hbox2, FALSE, FALSE, 0); + + { + ui::Widget label = ui::Label("Texture"); + label.show(); + hbox2.pack_start(label, FALSE, TRUE, 0); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + hbox2.pack_start(entry, TRUE, TRUE, 0); + m_texture = entry; + m_textureEntry.connect(entry); + GlobalTextureEntryCompletion::instance().connect(entry); + } + } + + + { + auto table = ui::Table(6, 4, FALSE); + table.show(); + vbox.pack_start(table, FALSE, FALSE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + ui::Widget label = ui::Label("Horizontal shift"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + } + { + auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 2); + m_hshiftIncrement.m_spin = spin; + m_hshiftSpinner.connect(spin); + spin.show(); + table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + spin.dimensions(60, -1); + } + { + ui::Widget label = ui::Label("Step"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {3, 4, 0, 1}, {GTK_EXPAND | GTK_FILL, 0}); + entry.dimensions(50, -1); + m_hshiftIncrement.m_entry = entry; + m_hshiftEntry.connect(entry); + } + { + ui::Widget label = ui::Label("Vertical shift"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0}); + } + { + auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 2); + m_vshiftIncrement.m_spin = spin; + m_vshiftSpinner.connect(spin); + spin.show(); + table.attach(spin, {1, 2, 1, 2}, {GTK_FILL, 0}); + spin.dimensions(60, -1); + } + { + ui::Widget label = ui::Label("Step"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {2, 3, 1, 2}, {GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {3, 4, 1, 2}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + m_vshiftIncrement.m_entry = entry; + m_vshiftEntry.connect(entry); + } + { + ui::Widget label = ui::Label("Horizontal stretch"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0}); + } + { + auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 5); + m_hscaleIncrement.m_spin = spin; + m_hscaleSpinner.connect(spin); + spin.show(); + table.attach(spin, {1, 2, 2, 3}, {GTK_FILL, 0}); + spin.dimensions(60, -1); + } + { + ui::Widget label = ui::Label("Step"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {2, 3, 2, 3}, {GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {3, 4, 2, 3}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + m_hscaleIncrement.m_entry = entry; + m_hscaleEntry.connect(entry); + } + { + ui::Widget label = ui::Label("Vertical stretch"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {0, 1, 3, 4}, {GTK_FILL, 0}); + } + { + auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 5); + m_vscaleIncrement.m_spin = spin; + m_vscaleSpinner.connect(spin); + spin.show(); + table.attach(spin, {1, 2, 3, 4}, {GTK_FILL, 0}); + spin.dimensions(60, -1); + } + { + ui::Widget label = ui::Label("Step"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {2, 3, 3, 4}, {GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {3, 4, 3, 4}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + m_vscaleIncrement.m_entry = entry; + m_vscaleEntry.connect(entry); + } + { + ui::Widget label = ui::Label("Rotate"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {0, 1, 4, 5}, {GTK_FILL, 0}); + } + { + auto spin = ui::SpinButton(ui::Adjustment(0, -8192, 8192, 2, 8, 0), 0, 2); + m_rotateIncrement.m_spin = spin; + m_rotateSpinner.connect(spin); + spin.show(); + table.attach(spin, {1, 2, 4, 5}, {GTK_FILL, 0}); + spin.dimensions(60, -1); + gtk_spin_button_set_wrap(spin, TRUE); + } + { + ui::Widget label = ui::Label("Step"); + label.show(); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + table.attach(label, {2, 3, 4, 5}, {GTK_FILL, 0}); + } + { + auto entry = ui::Entry(ui::New); + entry.show(); + table.attach(entry, {3, 4, 4, 5}, {GTK_FILL, 0}); + entry.dimensions(50, -1); + m_rotateIncrement.m_entry = entry; + m_rotateEntry.connect(entry); + } + { + // match grid button + ui::Widget button = ui::Button("Match Grid"); + button.show(); + table.attach(button, {2, 4, 5, 6}, {GTK_EXPAND | GTK_FILL, 0}); + button.connect("clicked", G_CALLBACK(OnBtnMatchGrid), 0); + } + } + + { + auto frame = ui::Frame("Texturing"); + frame.show(); + vbox.pack_start(frame, FALSE, FALSE, 0); + { + auto table = ui::Table(4, 4, FALSE); + table.show(); + frame.add(table); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + gtk_container_set_border_width(GTK_CONTAINER(table), 5); + { + ui::Widget label = ui::Label("Brush"); + label.show(); + table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0}); + } + { + ui::Widget label = ui::Label("Patch"); + label.show(); + table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0}); + } + { + ui::Widget label = ui::Label("Width"); + label.show(); + table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0}); + } + { + ui::Widget label = ui::Label("Height"); + label.show(); + table.attach(label, {3, 4, 0, 1}, {GTK_FILL, 0}); + } + { + ui::Widget button = ui::Button("Axial"); + button.show(); + table.attach(button, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + button.connect("clicked", + G_CALLBACK(OnBtnAxial), 0); + button.dimensions(60, -1); + } + { + ui::Widget button = ui::Button("Fit"); + button.show(); + table.attach(button, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + button.connect("clicked", + G_CALLBACK(OnBtnFaceFit), 0); + button.dimensions(60, -1); + } + { + ui::Widget button = ui::Button("CAP"); + button.show(); + table.attach(button, {0, 1, 3, 4}, {GTK_EXPAND | GTK_FILL, 0}); + button.connect("clicked", + G_CALLBACK(OnBtnPatchdetails), 0); + button.dimensions(60, -1); + } + { + ui::Widget button = ui::Button("Set..."); + button.show(); + table.attach(button, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0}); + button.connect("clicked", + G_CALLBACK(OnBtnPatchreset), 0); + button.dimensions(60, -1); + } + { + ui::Widget button = ui::Button("Natural"); + button.show(); + table.attach(button, {2, 3, 3, 4}, {GTK_EXPAND | GTK_FILL, 0}); + button.connect("clicked", + G_CALLBACK(OnBtnPatchnatural), 0); + button.dimensions(60, -1); + } + { + ui::Widget button = ui::Button("Fit"); + button.show(); + table.attach(button, {3, 4, 3, 4}, {GTK_EXPAND | GTK_FILL, 0}); + button.connect("clicked", + G_CALLBACK(OnBtnPatchFit), 0); + button.dimensions(60, -1); + } + { + auto spin = ui::SpinButton(ui::Adjustment(1, 0, 1 << 16, 1, 10, 0), 0, 6); + spin.show(); + table.attach(spin, {2, 3, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + spin.dimensions(60, -1); + AddDialogData(spin, m_fitHorizontal); + } + { + auto spin = ui::SpinButton(ui::Adjustment(1, 0, 1 << 16, 1, 10, 0), 0, 6); + spin.show(); + table.attach(spin, {3, 4, 1, 2}, {GTK_EXPAND | GTK_FILL, 0}); + spin.dimensions(60, -1); + AddDialogData(spin, m_fitVertical); + } + } + } + if (!string_empty(g_pGameDescription->getKeyValue("si_flags"))) { + { + auto frame = ui::Frame("Surface Flags"); + frame.show(); + vbox.pack_start(frame, TRUE, TRUE, 0); + { + auto vbox3 = ui::VBox(FALSE, 4); + //gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4); + vbox3.show(); + frame.add(vbox3); + { + auto table = ui::Table(8, 4, FALSE); + table.show(); + vbox3.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 0); + gtk_table_set_col_spacings(table, 0); + + GtkCheckButton **p = m_surfaceFlags; + + for (unsigned int c = 0; c != 4; ++c) { + for (unsigned int r = 0; r != 8; ++r) { + auto check = ui::CheckButton(getSurfaceFlagName(c * 8 + r)); + check.show(); + table.attach(check, {c, c + 1, r, r + 1}, {GTK_EXPAND | GTK_FILL, 0}); + *p++ = check; + guint handler_id = togglebutton_connect_toggled(check, ApplyFlagsCaller(*this)); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id)); + } + } + } + } + } + { + auto frame = ui::Frame("Content Flags"); + frame.show(); + vbox.pack_start(frame, TRUE, TRUE, 0); + { + auto vbox3 = ui::VBox(FALSE, 4); + //gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4); + vbox3.show(); + frame.add(vbox3); + { + + auto table = ui::Table(8, 4, FALSE); + table.show(); + vbox3.pack_start(table, TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 0); + gtk_table_set_col_spacings(table, 0); + + GtkCheckButton **p = m_contentFlags; + + for (unsigned int c = 0; c != 4; ++c) { + for (unsigned int r = 0; r != 8; ++r) { + auto check = ui::CheckButton(getContentFlagName(c * 8 + r)); + check.show(); + table.attach(check, {c, c + 1, r, r + 1}, {GTK_EXPAND | GTK_FILL, 0}); + *p++ = check; + guint handler_id = togglebutton_connect_toggled(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(ui::CheckButton::from(m_contentFlags[BRUSH_DETAIL_FLAG]), FALSE); + } + } + } + { + auto frame = ui::Frame("Value"); + frame.show(); + vbox.pack_start(frame, TRUE, TRUE, 0); + { + auto vbox3 = ui::VBox(FALSE, 4); + gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4); + vbox3.show(); + frame.add(vbox3); + + { + auto entry = ui::Entry(ui::New); + entry.show(); + vbox3.pack_start(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... + ui::Widget frame = ui::Frame( "Textool" ); + frame.show(); + vbox.pack_start( 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!) + g_object_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_can_focus( TexTool::g_textoolWin, true ); + // <-- end stuff... + TexTool::g_textoolWin.show(); + TexTool::g_textoolWin.dimensions( -1, 240 ); //Yeah! + frame.add(TexTool::g_textoolWin); + + TexTool::g_textoolWin.connect( "size_allocate", G_CALLBACK( TexTool::size_allocate ), NULL ); + TexTool::g_textoolWin.connect( "expose_event", G_CALLBACK( TexTool::expose ), NULL ); + TexTool::g_textoolWin.connect( "button_press_event", G_CALLBACK( TexTool::button_press ), NULL ); + TexTool::g_textoolWin.connect( "button_release_event", G_CALLBACK( TexTool::button_release ), NULL ); + TexTool::g_textoolWin.connect( "motion_notify_event", G_CALLBACK( TexTool::motion ), NULL ); + } + { + ui::Widget hbox = ui::HBox( FALSE, 5 ); + hbox.show(); + vbox.pack_start( hbox , FALSE, FALSE, 0 ); + // Checkboxes go here... (Flip X/Y) + ui::Widget flipX = ui::CheckButton( "Flip X axis" ); + ui::Widget flipY = ui::CheckButton( "Flip Y axis" ); + flipX.show(); + flipY.show(); + hbox.pack_start( flipX, FALSE, FALSE, 0 ); + hbox.pack_start( 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 !!! +// frame.add(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(flipX.connect("toggled", G_CALLBACK(TexTool::flipX), 0))); +// g_object_set_data(G_OBJECT(flipY), "handler", gint_to_pointer(flipY.connect("toggled", G_CALLBACK(TexTool::flipY), 0))); +//Instead, just do: + flipX.connect( "toggled", G_CALLBACK( TexTool::flipX ), NULL ); + flipY.connect( "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(ui::SpinButton 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(ui::SpinButton spin, gdouble value) +{ + auto adjust = gtk_spin_button_get_adjustment(spin); + gtk_adjustment_set_step_increment(adjust, value); +} + +void SurfaceInspector::Update() +{ + const char *name = SurfaceInspector_GetSelectedShader(); + + if (shader_is_texture(name)) { + m_texture.text(shader_get_textureName(name)); + } else { + m_texture.text(""); + } + + 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(ui::CheckButton::from(*p), + flags.m_surfaceFlags & (1 << (p - m_surfaceFlags))); + } + + for (GtkCheckButton **p = m_contentFlags; p != m_contentFlags + 32; ++p) { + toggle_button_set_active_no_signal(ui::CheckButton::from(*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(m_hshiftIncrement.m_spin)); + shiftScaleRotate.shift[1] = static_cast( gtk_spin_button_get_value(m_vshiftIncrement.m_spin)); + shiftScaleRotate.scale[0] = static_cast( gtk_spin_button_get_value(m_hscaleIncrement.m_spin)); + shiftScaleRotate.scale[1] = static_cast( gtk_spin_button_get_value(m_vscaleIncrement.m_spin)); + shiftScaleRotate.rotate = static_cast( gtk_spin_button_get_value(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, true); +} + +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 Function FaceGetTexture; + +void +Face_setTexture(Face &face, const char *shader, const TextureProjection &projection, const ContentsFlagsValue &flags) +{ + face.SetShader(shader); + face.SetTexdef(projection, false); + face.SetFlags(flags); +} + +typedef Function 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 Function PatchGetTexture; + +void +Patch_setTexture(Patch &patch, const char *shader, const TextureProjection &projection, const ContentsFlagsValue &flags) +{ + patch.SetShader(shader); +} + +typedef Function PatchSetTexture; + + +typedef Callback GetTextureCallback; +typedef Callback 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 = makeCallback(FaceSetTexture(), face); + texturable.getTexture = makeCallback(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 = makeCallback(PatchSetTexture(), *patch); + m_texturable.getTexture = makeCallback(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, false); + 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(makeCallbackF(SurfaceInspector_constructPage)); +} + +void SurfaceInspector_registerCommands() +{ + GlobalCommands_insert("FitTexture", makeCallbackF(SurfaceInspector_FitTexture), + Accelerator('B', (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("SurfaceInspector", makeCallbackF(SurfaceInspector_toggleShown), Accelerator('S')); + + GlobalCommands_insert("FaceCopyTexture", makeCallbackF(SelectedFaces_copyTexture)); + GlobalCommands_insert("FacePasteTexture", makeCallbackF(SelectedFaces_pasteTexture)); +} + + +#include "preferencesystem.h" + + +void SurfaceInspector_Construct() +{ + g_SurfaceInspector = new SurfaceInspector; + + SurfaceInspector_registerCommands(); + + FaceTextureClipboard_setDefault(); + + GlobalPreferenceSystem().registerPreference("SurfaceWnd", make_property( + getSurfaceInspector().m_positionTracker)); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Scale1", make_property_string(g_si_globals.scale[0])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Scale2", make_property_string(g_si_globals.scale[1])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Shift1", make_property_string(g_si_globals.shift[0])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Shift2", make_property_string(g_si_globals.shift[1])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Rotate", make_property_string(g_si_globals.rotate)); + GlobalPreferenceSystem().registerPreference("SnapTToGrid", make_property_string(g_si_globals.m_bSnapTToGrid)); + + typedef FreeCaller 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 < numPts; i++ ) + glVertex2f( pts[i].x(), pts[i].y() ); + + glEnd(); +} + +// Note: Setup and all that jazz must be done by the caller! + +void DrawCircularArc( Vector2 ctr, float startAngle, float endAngle, float radius ){ + float stepSize = ( 2.0f * PI ) / 200.0f; + + for ( float angle = startAngle; angle <= endAngle; angle += stepSize ) + glVertex2f( ctr.x() + radius * cos( angle ), ctr.y() + radius * sin( angle ) ); +} + + +void focus(){ + if ( numPts == 0 ) { + return; + } + + // Find selected texture's extents... + + extents.minX = extents.maxX = pts[0].x(), + extents.minY = extents.maxY = pts[0].y(); + + for ( int i = 1; i < numPts; i++ ) + { + if ( pts[i].x() < extents.minX ) { + extents.minX = pts[i].x(); + } + if ( pts[i].x() > 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( ui::Widget win, GtkAllocation * a, gpointer ){ + windowSize.x() = a->width; + windowSize.y() = a->height; + queueDraw(); + return false; +} + +gboolean expose( ui::Widget 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( ui::Widget 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( ui::Widget 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( ui::ToggleButton, 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( ui::ToggleButton, 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 0000000..739a1bf --- /dev/null +++ b/radiant/surfacedialog.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 + */ + +#include + +#if !defined( INCLUDED_SURFACEDIALOG_H ) +#define INCLUDED_SURFACEDIALOG_H + + +void SurfaceInspector_Construct(); + +void SurfaceInspector_Destroy(); + +void SurfaceInspector_constructWindow(ui::Window 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 0000000..5f66bb2 --- /dev/null +++ b/radiant/texmanip.cpp @@ -0,0 +1,343 @@ +/* + 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 0000000..cd01ab0 --- /dev/null +++ b/radiant/texmanip.h @@ -0,0 +1,42 @@ +/* + 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 0000000..04512f6 --- /dev/null +++ b/radiant/textureentry.cpp @@ -0,0 +1,72 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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" + +#include + +template +void EntryCompletion::connect(ui::Entry entry) +{ + if (!m_store) { + m_store = ui::ListStore::from(gtk_list_store_new(1, G_TYPE_STRING)); + + fill(); + + StringList().connect(IdleDraw::QueueDrawCaller(m_idleUpdate)); + } + + auto completion = ui::EntryCompletion::from(gtk_entry_completion_new()); + gtk_entry_set_completion(entry, completion); + gtk_entry_completion_set_model(completion, m_store); + gtk_entry_completion_set_text_column(completion, 0); +} + +template +void EntryCompletion::append(const char *string) +{ + m_store.append(0, string); +} + +template +void EntryCompletion::fill() +{ + StringList().forEach(AppendCaller(*this)); +} + +template +void EntryCompletion::clear() +{ + m_store.clear(); +} + +template +void EntryCompletion::update() +{ + clear(); + fill(); +} + +template +class EntryCompletion; + +template +class EntryCompletion; diff --git a/radiant/textureentry.h b/radiant/textureentry.h new file mode 100644 index 0000000..ed2f9f5 --- /dev/null +++ b/radiant/textureentry.h @@ -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 + */ + +#if !defined( INCLUDED_TEXTUREENTRY_H ) +#define INCLUDED_TEXTUREENTRY_H + +#include "gtkutil/idledraw.h" + +#include "generic/static.h" +#include "signal/isignal.h" +#include "shaderlib.h" + +#include "texwindow.h" + +template +class EntryCompletion { + ui::ListStore m_store; + IdleDraw m_idleUpdate; +public: + EntryCompletion() : m_store(ui::null), m_idleUpdate(UpdateCaller(*this)) + { + } + + void connect(ui::Entry entry); + + void append(const char *string); + + using AppendCaller = MemberCaller; + + void fill(); + + void clear(); + + void update(); + + using UpdateCaller = MemberCaller; +}; + +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 > GlobalTextureEntryCompletion; + + +class ShaderList { +public: + void forEach(const ShaderNameCallback &callback) const + { + GlobalShaderSystem().foreachShaderName(callback); + } + + void connect(const SignalHandler &update) const + { + TextureBrowser_addShadersRealiseCallback(update); + } +}; + +typedef Static > GlobalShaderEntryCompletion; + + +#endif diff --git a/radiant/textures.cpp b/radiant/textures.cpp new file mode 100644 index 0000000..50e0374 --- /dev/null +++ b/radiant/textures.cpp @@ -0,0 +1,901 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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) +{ + float maxAniso = QGL_maxTextureAnisotropy(); + if (maxAniso > 1) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + } else if (mode == eTextures_MAX_ANISOTROPY) { + mode = eTextures_LINEAR_MIPMAP_LINEAR; + } + + 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, maxAniso); + break; + default: + globalOutputStream() << "invalid texture mode\n"; + } +} + +ETexturesMode g_texture_mode = eTextures_NEAREST_MIPMAP_NEAREST; + + +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; +LatchedValue 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: + virtual ~TexturesMap() = default; + + 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; + } + + switch (g_texture_globals.texture_components) { + case GL_RGBA: + break; + case GL_COMPRESSED_RGBA_ARB: + if (!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; + g_texture_globals.texture_components = GL_RGBA; + } + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + if (!g_texture_globals.m_bS3CompressionSupported) { + globalOutputStream() + << "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n"; + if (g_texture_globals.m_bOpenGLCompressionSupported) { + g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA; + g_texture_globals.texture_components = GL_COMPRESSED_RGBA_ARB; + } else { + g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE; + g_texture_globals.texture_components = GL_RGBA; + } + } + break; + default: + globalOutputStream() << "Unknown texture compression selected, reverting\n"; + g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE; + g_texture_globals.texture_components = GL_RGBA; + break; + } + + + 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 ¬ify) +{ + 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; + + 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; + } + } + + Textures_setTextureComponents(texture_components); +} + +struct TextureCompression { + static void Export(const TextureCompressionFormat &self, const Callback &returnz) + { + returnz(self); + } + + static void Import(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(); + } +}; + +struct TextureGamma { + static void Export(const float &self, const Callback &returnz) + { + returnz(self); + } + + static void Import(float &self, float value) + { + if (value != self) { + Textures_Unrealise(); + self = value; + Textures_Realise(); + } + } +}; + +struct TextureMode { + static void Export(const ETexturesMode &self, const Callback &returnz) + { + switch (self) { + case eTextures_NEAREST: + returnz(0); + break; + case eTextures_NEAREST_MIPMAP_NEAREST: + returnz(1); + break; + case eTextures_LINEAR: + returnz(2); + break; + case eTextures_NEAREST_MIPMAP_LINEAR: + returnz(3); + break; + case eTextures_LINEAR_MIPMAP_NEAREST: + returnz(4); + break; + case eTextures_LINEAR_MIPMAP_LINEAR: + returnz(5); + break; + case eTextures_MAX_ANISOTROPY: + returnz(6); + break; + default: + returnz(4); + } + } + + static void Import(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); + } + } +}; + +void Textures_constructPreferences(PreferencesPage &page) +{ + { + const char *percentages[] = {"12.5%", "25%", "50%", "100%",}; + page.appendRadio( + "Texture Quality", + STRING_ARRAY_RANGE(percentages), + make_property(g_Textures_textureQuality) + ); + } + page.appendSpinner( + "Texture Gamma", + 1.0, + 0.0, + 1.0, + make_property(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), + make_property(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, + make_property(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(makeCallbackF(Textures_constructPage)); +} + +struct TextureCompressionPreference { + static void Export(const Callback &returnz) + { + returnz(g_texture_globals.m_nTextureCompressionFormat); + } + + static void Import(int value) + { + g_texture_globals.m_nTextureCompressionFormat = static_cast( value ); + Textures_UpdateTextureCompressionFormat(); + } +}; + +void Textures_Construct() +{ + g_texturesmap = new TexturesMap; + + GlobalPreferenceSystem().registerPreference("TextureCompressionFormat", + make_property_string()); + GlobalPreferenceSystem().registerPreference("TextureFiltering", + make_property_string(reinterpret_cast( g_texture_mode ))); + GlobalPreferenceSystem().registerPreference("TextureQuality", + make_property_string(g_Textures_textureQuality.m_latched)); + GlobalPreferenceSystem().registerPreference("SI_Gamma", make_property_string(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 0000000..35f22f3 --- /dev/null +++ b/radiant/textures.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_TEXTURES_H ) +#define INCLUDED_TEXTURES_H + +#include "generic/callback.h" + +void Textures_Realise(); + +void Textures_Unrealise(); + +void Textures_sharedContextDestroyed(); + +void Textures_setModeChangedNotify(const Callback ¬ify); + +#endif diff --git a/radiant/texwindow.cpp b/radiant/texwindow.cpp new file mode 100644 index 0000000..a0b5134 --- /dev/null +++ b/radiant/texwindow.cpp @@ -0,0 +1,2973 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +#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 "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" + +#define NOTEX_BASENAME "notex" +#define SHADERNOTEX_BASENAME "shadernotex" + +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 ReferenceCaller TextureGroupsAddWadCaller; + +namespace { + bool g_TextureBrowser_shaderlistOnly = false; + bool g_TextureBrowser_fixedSize = true; + bool g_TextureBrowser_filterMissing = false; + bool g_TextureBrowser_filterFallback = true; + bool g_TextureBrowser_enableAlpha = true; +} + +CopiedString g_notex; +CopiedString g_shadernotex; + +bool isMissing(const char *name); + +bool isNotex(const char *name); + +bool isMissing(const char *name) +{ + if (string_equal(g_notex.c_str(), name)) { + return true; + } + if (string_equal(g_shadernotex.c_str(), name)) { + return true; + } + return false; +} + +bool isNotex(const char *name) +{ + if (string_equal_suffix(name, "/" NOTEX_BASENAME)) { + return true; + } + if (string_equal_suffix(name, "/" SHADERNOTEX_BASENAME)) { + return true; + } + return false; +} + +void TextureGroups_addShader(TextureGroups &groups, const char *shaderName) +{ + const char *texture = path_make_relative(shaderName, "textures/"); + + // hide notex / shadernotex images + if (g_TextureBrowser_filterFallback) { + if (isNotex(shaderName)) { + return; + } + if (isNotex(texture)) { + return; + } + } + + if (texture != shaderName) { + const char *last = path_remove_directory(texture); + if (!string_empty(last)) { + groups.insert(CopiedString(StringRange(texture, --last))); + } + } +} + +typedef ReferenceCaller TextureGroupsAddShaderCaller; + +void TextureGroups_addDirectory(TextureGroups &groups, const char *directory) +{ + groups.insert(directory); +} + +typedef ReferenceCaller TextureGroupsAddDirectoryCaller; + +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(ui::Adjustment adjustment, DeferredAdjustment *self) + { + self->value_changed(gtk_adjustment_get_value(adjustment)); + } +}; + + +class TextureBrowser; + +typedef ReferenceCaller TextureBrowserQueueDrawCaller; + +void TextureBrowser_scrollChanged(void *data, gdouble value); + + +enum StartupShaders { + STARTUPSHADERS_NONE = 0, + STARTUPSHADERS_COMMON, +}; + +void TextureBrowser_hideUnusedExport(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport; + +void TextureBrowser_showShadersExport(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport; + +void TextureBrowser_showShaderlistOnly(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport; + +void TextureBrowser_fixedSize(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_fixedSize> TextureBrowserFixedSizeExport; + +void TextureBrowser_filterMissing(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_filterMissing> TextureBrowserFilterMissingExport; + +void TextureBrowser_filterFallback(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_filterFallback> TextureBrowserFilterFallbackExport; + +void TextureBrowser_enableAlpha(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_enableAlpha> TextureBrowserEnableAlphaExport; + +class TextureBrowser { +public: + int width, height; + int originy; + int m_nTotalHeight; + + CopiedString shader; + + ui::Window m_parent{ui::null}; + ui::GLArea m_gl_widget{ui::null}; + ui::Widget m_texture_scroll{ui::null}; + ui::TreeView m_treeViewTree{ui::New}; + ui::TreeView m_treeViewTags{ui::null}; + ui::Frame m_tag_frame{ui::null}; + ui::ListStore m_assigned_store{ui::null}; + ui::ListStore m_available_store{ui::null}; + ui::TreeView m_assigned_tree{ui::null}; + ui::TreeView m_available_tree{ui::null}; + ui::Widget m_scr_win_tree{ui::null}; + ui::Widget m_scr_win_tags{ui::null}; + ui::Widget m_tag_notebook{ui::null}; + ui::Button m_search_button{ui::null}; + ui::Widget m_shader_info_item{ui::null}; + + std::set m_all_tags; + ui::ListStore m_all_tags_list{ui::null}; + std::vector m_copied_tags; + std::set m_found_shaders; + + ToggleItem m_hideunused_item; + ToggleItem m_hidenotex_item; + ToggleItem m_showshaders_item; + ToggleItem m_showshaderlistonly_item; + ToggleItem m_fixedsize_item; + ToggleItem m_filternotex_item; + ToggleItem m_enablealpha_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(ui::null), + m_hideunused_item(TextureBrowserHideUnusedExport()), + m_hidenotex_item(TextureBrowserFilterFallbackExport()), + m_showshaders_item(TextureBrowserShowShadersExport()), + m_showshaderlistonly_item(TextureBrowserShowShaderlistOnlyExport()), + m_fixedsize_item(TextureBrowserFixedSizeExport()), + m_filternotex_item(TextureBrowserFilterMissingExport()), + m_enablealpha_item(TextureBrowserEnableAlphaExport()), + 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(96) + { + } +}; + +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_font->getPixelHeight(); +} + +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; + } +} + +// if texture_showinuse jump over non in-use textures +bool Texture_IsShown(IShader *shader, bool show_shaders, bool hideUnused) +{ + // filter missing shaders + // ugly: filter on built-in fallback name after substitution + if (g_TextureBrowser_filterMissing) { + if (isMissing(shader->getTexture()->name)) { + return false; + } + } + // filter the fallback (notex/shadernotex) for missing shaders or editor image + if (g_TextureBrowser_filterFallback) { + if (isNotex(shader->getName())) { + return false; + } + if (isNotex(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); +} + +void TextureBrowser_constructTreeStore(); + +class ShadersObserver : public ModuleObserver { + Signal0 m_realiseCallbacks; +public: + void realise() + { + m_realiseCallbacks(); + TextureBrowser_constructTreeStore(); + } + + 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(); +} + +struct TextureBrowser_ShowScrollbar { + static void Export(const TextureBrowser &self, const Callback &returnz) + { + returnz(self.m_showTextureScrollbar); + } + + static void Import(TextureBrowser &self, bool value) + { + self.m_showTextureScrollbar = value; + if (self.m_texture_scroll) { + self.m_texture_scroll.visible(self.m_showTextureScrollbar); + TextureBrowser_updateScroll(self); + } + } +}; + + +/* + ============== + 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 endswith(const char *haystack, const char *needle) +{ + size_t lh = strlen(haystack); + size_t ln = strlen(needle); + if (lh < ln) { + return false; + } + return !memcmp(haystack + (lh - ln), needle, ln); +} + +bool texture_name_ignore(const char *name) +{ + StringOutputStream strTemp(string_length(name)); + strTemp << LowerCase(name); + + return + endswith(strTemp.c_str(), ".specular") || + endswith(strTemp.c_str(), ".glow") || + endswith(strTemp.c_str(), ".bump") || + endswith(strTemp.c_str(), ".diffuse") || + endswith(strTemp.c_str(), ".blend") || + endswith(strTemp.c_str(), ".alpha") || + endswith(strTemp.c_str(), "_alpha") || + /* Quetoo */ + endswith(strTemp.c_str(), "_h") || + endswith(strTemp.c_str(), "_local") || + endswith(strTemp.c_str(), "_nm") || + endswith(strTemp.c_str(), "_s") || + /* DarkPlaces */ + endswith(strTemp.c_str(), "_bump") || + endswith(strTemp.c_str(), "_glow") || + endswith(strTemp.c_str(), "_gloss") || + endswith(strTemp.c_str(), "_luma") || + endswith(strTemp.c_str(), "_norm") || + endswith(strTemp.c_str(), "_pants") || + endswith(strTemp.c_str(), "_shirt") || + endswith(strTemp.c_str(), "_reflect") || + /* Unvanquished */ + endswith(strTemp.c_str(), "_d") || + endswith(strTemp.c_str(), "_n") || + endswith(strTemp.c_str(), "_p") || + endswith(strTemp.c_str(), "_g") || + endswith(strTemp.c_str(), "_a") || + 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); + +ui::Widget g_page_textures{ui::null}; + +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: + using func = void(const char *); + + 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 ConstPointerCaller 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(makeCallback(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(makeCallback(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 Callback &importer) +{ + importer(TextureBrowser_hideUnused()); +} + +typedef FreeCaller &), TextureBrowser_hideUnusedExport> TextureBrowserHideUnusedExport; + +void TextureBrowser_showShadersExport(const Callback &importer) +{ + importer(GlobalTextureBrowser().m_showShaders); +} + +typedef FreeCaller &), TextureBrowser_showShadersExport> TextureBrowserShowShadersExport; + +void TextureBrowser_showShaderlistOnly(const Callback &importer) +{ + importer(g_TextureBrowser_shaderlistOnly); +} + +typedef FreeCaller &), TextureBrowser_showShaderlistOnly> TextureBrowserShowShaderlistOnlyExport; + +void TextureBrowser_fixedSize(const Callback &importer) +{ + importer(g_TextureBrowser_fixedSize); +} + +typedef FreeCaller &), TextureBrowser_fixedSize> TextureBrowser_FixedSizeExport; + +void TextureBrowser_filterMissing(const Callback &importer) +{ + importer(g_TextureBrowser_filterMissing); +} + +typedef FreeCaller &), TextureBrowser_filterMissing> TextureBrowser_filterMissingExport; + +void TextureBrowser_filterFallback(const Callback &importer) +{ + importer(g_TextureBrowser_filterFallback); +} + +typedef FreeCaller &), TextureBrowser_filterFallback> TextureBrowser_filterFallbackExport; + +void TextureBrowser_enableAlpha(const Callback &importer) +{ + importer(g_TextureBrowser_enableAlpha); +} + +typedef FreeCaller &), TextureBrowser_enableAlpha> TextureBrowser_enableAlphaExport; + +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); + if (g_TextureBrowser_enableAlpha) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } else { + 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 checkerboard for transparent textures + if (g_TextureBrowser_enableAlpha) { + glDisable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + int font_height = TextureBrowser_fontHeight(textureBrowser); + for (int i = 0; i < nHeight; i += 8) { + for (int j = 0; j < nWidth; j += 8) { + unsigned char color = (i + j) / 8 % 2 ? 0x66 : 0x99; + glColor3ub(color, color, color); + int left = j; + int right = std::min(j + 8, nWidth); + int top = i; + int bottom = std::min(i + 8, nHeight); + glVertex2i(x + right, y - nHeight - font_height + top); + glVertex2i(x + left, y - nHeight - font_height + top); + glVertex2i(x + left, y - nHeight - font_height + bottom); + glVertex2i(x + right, y - nHeight - font_height + bottom); + } + } + 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 Texture_DrawSingle(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); + if (g_TextureBrowser_enableAlpha) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } else { + 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 (!shader_equal(TextureBrowser_GetSelectedShader(textureBrowser), shader->getName())) { + 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 + + 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); + + // draw checkerboard for transparent textures + if (g_TextureBrowser_enableAlpha) { + glDisable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + int font_height = TextureBrowser_fontHeight(textureBrowser); + for (int i = 0; i < nHeight; i += 8) { + for (int j = 0; j < nWidth; j += 8) { + unsigned char color = (i + j) / 8 % 2 ? 0x66 : 0x99; + glColor3ub(color, color, color); + int left = j; + int right = std::min(j + 8, nWidth); + int top = i; + int bottom = std::min(i + 8, nHeight); + glVertex2i(x + right, y - nHeight - font_height + top); + glVertex2i(x + left, y - nHeight - font_height + top); + glVertex2i(x + left, y - nHeight - font_height + bottom); + glVertex2i(x + right, y - nHeight - font_height + bottom); + } + } + 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) { + 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_setUniformSize(TextureBrowser &textureBrowser, std::size_t scale) +{ + textureBrowser.m_uniformTextureSize = 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(ui::ListStore store, const char *shader, TextureBrowser *textureBrowser) +{ + GtkTreeIter iter; + + store.clear(); + + std::vector assigned_tags; + TagBuilder.GetShaderTags(shader, assigned_tags); + + for (size_t i = 0; i < assigned_tags.size(); i++) { + store.append(TAG_COLUMN, assigned_tags[i].c_str()); + } +} + +void BuildStoreAvailableTags(ui::ListStore storeAvailable, + ui::ListStore storeAssigned, + const std::set &allTags, + TextureBrowser *textureBrowser) +{ + GtkTreeIter iterAssigned; + GtkTreeIter iterAvailable; + std::set::const_iterator iterAll; + gchar *tag_assigned; + + storeAvailable.clear(); + + bool row = gtk_tree_model_get_iter_first(storeAssigned, &iterAssigned) != 0; + + if (!row) { // does the shader have tags assigned? + for (iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll) { + storeAvailable.append(TAG_COLUMN, (*iterAll).c_str()); + } + } else { + while (row) // available tags = all tags - assigned tags + { + gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1); + + for (iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll) { + if (strcmp((char *) tag_assigned, (*iterAll).c_str()) != 0) { + storeAvailable.append(TAG_COLUMN, (*iterAll).c_str()); + } else { + row = gtk_tree_model_iter_next(storeAssigned, &iterAssigned) != 0; + + if (row) { + gtk_tree_model_get(storeAssigned, &iterAssigned, TAG_COLUMN, &tag_assigned, -1); + } + } + } + } + } +} + +gboolean TextureBrowser_button_press(ui::Widget 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; + textureBrowser->m_tag_frame.show(); + + ui::process(); + + 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; + textureBrowser->m_tag_frame.hide(); + } + } + } + return FALSE; +} + +gboolean TextureBrowser_button_release(ui::Widget 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(ui::Widget widget, GdkEventMotion *event, TextureBrowser *textureBrowser) +{ + return FALSE; +} + +gboolean TextureBrowser_scroll(ui::Widget 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(ui::Adjustment adjustment, TextureBrowser *textureBrowser) +{ + textureBrowser->m_scrollAdjustment.value_changed(gtk_adjustment_get_value(adjustment)); +} + +void TextureBrowser_updateScroll(TextureBrowser &textureBrowser) +{ + if (textureBrowser.m_showTextureScrollbar) { + int totalHeight = TextureBrowser_TotalHeight(textureBrowser); + + totalHeight = std::max(totalHeight, textureBrowser.height); + + auto vadjustment = gtk_range_get_adjustment(GTK_RANGE(textureBrowser.m_texture_scroll)); + + gtk_adjustment_set_value(vadjustment, -TextureBrowser_getOriginY(textureBrowser)); + gtk_adjustment_set_page_size(vadjustment, textureBrowser.height); + gtk_adjustment_set_page_increment(vadjustment, textureBrowser.height / 2); + gtk_adjustment_set_step_increment(vadjustment, 20); + gtk_adjustment_set_lower(vadjustment, 0); + gtk_adjustment_set_upper(vadjustment, totalHeight); + + g_signal_emit_by_name(G_OBJECT(vadjustment), "changed"); + } +} + +gboolean TextureBrowser_size_allocate(ui::Widget 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(ui::Widget 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, ui::TreeStore 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(); + auto store = ui::TreeStore::from(gtk_tree_store_new(1, G_TYPE_STRING)); + TextureGroups_constructTreeModel(groups, store); + + gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTree, store); + + g_object_unref(G_OBJECT(store)); +} + +void TextureBrowser_constructTreeStoreTags() +{ + TextureGroups groups; + auto store = ui::TreeStore::from(gtk_tree_store_new(1, G_TYPE_STRING)); + auto model = g_TextureBrowser.m_all_tags_list; + + gtk_tree_view_set_model(g_TextureBrowser.m_treeViewTags, model); + + g_object_unref(G_OBJECT(store)); +} + +void TreeView_onRowActivated(ui::TreeView treeview, ui::TreePath path, ui::TreeViewColumn col, gpointer userdata) +{ + GtkTreeIter iter; + + auto model = gtk_tree_view_get_model(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() +{ + gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTree, FALSE); + + gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTree, FALSE); + g_TextureBrowser.m_treeViewTree.connect("row-activated", (GCallback) TreeView_onRowActivated, NULL); + + auto renderer = ui::CellRendererText(ui::New); + gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTree, -1, "", renderer, "text", 0, NULL); + + TextureBrowser_constructTreeStore(); +} + +void TextureBrowser_addTag(); + +void TextureBrowser_renameTag(); + +void TextureBrowser_deleteTag(); + +void TextureBrowser_createContextMenu(ui::Widget treeview, GdkEventButton *event) +{ + ui::Widget menu = ui::Menu(ui::New); + + ui::Widget menuitem = ui::MenuItem("Add tag"); + menuitem.connect("activate", (GCallback) TextureBrowser_addTag, treeview); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = ui::MenuItem("Rename tag"); + menuitem.connect("activate", (GCallback) TextureBrowser_renameTag, treeview); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = ui::MenuItem("Delete tag"); + menuitem.connect("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(ui::TreeView treeview, GdkEventButton *event) +{ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + GtkTreePath *path; + auto selection = gtk_tree_view_get_selection(treeview); + + if (gtk_tree_view_get_path_at_pos(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() +{ + g_TextureBrowser.m_treeViewTags = ui::TreeView(ui::New); + gtk_tree_view_set_enable_search(g_TextureBrowser.m_treeViewTags, FALSE); + + g_TextureBrowser.m_treeViewTags.connect("button-press-event", (GCallback) TreeViewTags_onButtonPressed, NULL); + + gtk_tree_view_set_headers_visible(g_TextureBrowser.m_treeViewTags, FALSE); + + auto renderer = ui::CellRendererText(ui::New); + gtk_tree_view_insert_column_with_attributes(g_TextureBrowser.m_treeViewTags, -1, "", renderer, "text", 0, NULL); + + TextureBrowser_constructTreeStoreTags(); +} + +ui::MenuItem TextureBrowser_constructViewMenu(ui::Menu menu) +{ + ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic("_View")); + + /*if (g_Layout_enableOpenStepUX.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", "FilterMissing"); + } + + // hide notex and shadernotex on texture browser: no one wants to apply them + create_check_menu_item_with_mnemonic(menu, "Hide Fallback", "FilterFallback"); + + 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 (!string_empty(g_pGameDescription->getKeyValue("show_wads"))) { + g_TextureBrowser.m_showShaders = true; + } else { + create_check_menu_item_with_mnemonic(menu, "Show shaders", "ToggleShowShaders"); + } + + if (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"); + } + + menu_separator(menu); + create_check_menu_item_with_mnemonic(menu, "Fixed Size", "FixedSize"); + create_check_menu_item_with_mnemonic(menu, "Transparency", "EnableAlpha"); + + if (string_empty(g_pGameDescription->getKeyValue("show_wads"))) { + menu_separator(menu); + g_TextureBrowser.m_shader_info_item = ui::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; +} + +ui::MenuItem TextureBrowser_constructToolsMenu(ui::Menu menu) +{ + ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic("_Tools")); + + /*if (g_Layout_enableOpenStepUX.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; +} + +ui::MenuItem TextureBrowser_constructTagsMenu(ui::Menu menu) +{ + ui::MenuItem textures_menu_item = ui::MenuItem(new_sub_menu_item_with_mnemonic("T_ags")); + + /*if (g_Layout_enableOpenStepUX.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(ui::TreeModel model, ui::TreePath path, GtkTreeIter iter, GSList **selected) +{ + g_assert(selected != NULL); + + auto 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; + + auto selection = gtk_tree_view_get_selection(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) { + auto path = gtk_tree_row_reference_get_path((GtkTreeRowReference *) node->data); + + if (path) { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter(g_TextureBrowser.m_available_store, &iter, path)) { + gtk_tree_model_get(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); + g_TextureBrowser.m_assigned_store.append(TAG_COLUMN, tag_assigned); + } + } + } + + 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; + + auto selection = gtk_tree_view_get_selection(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) { + auto path = gtk_tree_row_reference_get_path((GtkTreeRowReference *) node->data); + + if (path) { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter(g_TextureBrowser.m_assigned_store, &iter, path)) { + gtk_tree_model_get(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() +{ + g_TextureBrowser.m_all_tags_list.clear(); + + std::set::iterator iter; + + for (iter = g_TextureBrowser.m_all_tags.begin(); iter != g_TextureBrowser.m_all_tags.end(); ++iter) { + g_TextureBrowser.m_all_tags_list.append(TAG_COLUMN, (*iter).c_str()); + } +} + +void TextureBrowser_searchTags() +{ + GSList *selected = NULL; + GSList *node; + gchar *tag; + char buffer[256]; + char tags_searched[256]; + + auto selection = gtk_tree_view_get_selection(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) { + auto path = gtk_tree_row_reference_get_path((GtkTreeRowReference *) node->data); + + if (path) { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter(g_TextureBrowser.m_all_tags_list, &iter, path)) { + gtk_tree_model_get(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 " << (unsigned int) 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 { + g_TextureBrowser.m_search_button.hide(); + } +} + +void TextureBrowser_constructTagNotebook() +{ + g_TextureBrowser.m_tag_notebook = ui::Widget::from(gtk_notebook_new()); + ui::Widget labelTags = ui::Label("Tags"); + ui::Widget labelTextures = ui::Label("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_TextureBrowser.m_tag_notebook.connect("switch-page", G_CALLBACK(TextureBrowser_toggleSearchButton), NULL); + + gtk_widget_show_all(g_TextureBrowser.m_tag_notebook); +} + +void TextureBrowser_constructSearchButton() +{ + auto image = ui::Widget::from(gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR)); + g_TextureBrowser.m_search_button = ui::Button(ui::New); + g_TextureBrowser.m_search_button.connect("clicked", G_CALLBACK(TextureBrowser_searchTags), NULL); + gtk_widget_set_tooltip_text(g_TextureBrowser.m_search_button, "Search with selected tags"); + g_TextureBrowser.m_search_button.add(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_BASENAME ".xpm"; + g_notex = name.c_str(); + + name = StringOutputStream(256); + name << GlobalRadiant().getAppPath() << "bitmaps/" SHADERNOTEX_BASENAME " .xpm"; + g_shadernotex = name.c_str(); +} + +ui::Widget TextureBrowser_constructWindow(ui::Window 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; + + auto table = ui::Table(3, 3, FALSE); + auto vbox = ui::VBox(FALSE, 0); + table.attach(vbox, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL}); + vbox.show(); + + ui::Widget menu_bar{ui::null}; + + { // menu bar + menu_bar = ui::Widget::from(gtk_menu_bar_new()); + auto menu_view = ui::Menu(ui::New); + auto view_item = TextureBrowser_constructViewMenu(menu_view); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(view_item), menu_view); + gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), view_item); + + auto menu_tools = ui::Menu(ui::New); + auto tools_item = TextureBrowser_constructToolsMenu(menu_tools); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(tools_item), menu_tools); + gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), tools_item); + + table.attach(menu_bar, {0, 3, 0, 1}, {GTK_FILL, GTK_SHRINK}); + menu_bar.show(); + } + { // Texture TreeView + g_TextureBrowser.m_scr_win_tree = ui::ScrolledWindow(ui::New); + 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); + + g_TextureBrowser.m_scr_win_tree.show(); + + TextureBrowser_createTreeViewTree(); + + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tree), + g_TextureBrowser.m_treeViewTree); + g_TextureBrowser.m_treeViewTree.show(); + } + { // gl_widget scrollbar + auto w = ui::Widget::from(gtk_vscrollbar_new(ui::Adjustment(0, 0, 0, 1, 1, 0))); + table.attach(w, {2, 3, 1, 2}, {GTK_SHRINK, GTK_FILL}); + w.show(); + g_TextureBrowser.m_texture_scroll = w; + + auto vadjustment = ui::Adjustment::from(gtk_range_get_adjustment(GTK_RANGE(g_TextureBrowser.m_texture_scroll))); + vadjustment.connect("value_changed", G_CALLBACK(TextureBrowser_verticalScroll), &g_TextureBrowser); + + g_TextureBrowser.m_texture_scroll.visible(g_TextureBrowser.m_showTextureScrollbar); + } + { // gl_widget + g_TextureBrowser.m_gl_widget = glwidget_new(FALSE); + g_object_ref(g_TextureBrowser.m_gl_widget._handle); + + 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_can_focus(g_TextureBrowser.m_gl_widget, true); + + table.attach(g_TextureBrowser.m_gl_widget, {1, 2, 1, 2}); + g_TextureBrowser.m_gl_widget.show(); + + g_TextureBrowser.m_sizeHandler = g_TextureBrowser.m_gl_widget.connect("size_allocate", + G_CALLBACK(TextureBrowser_size_allocate), + &g_TextureBrowser); + g_TextureBrowser.m_exposeHandler = g_TextureBrowser.m_gl_widget.on_render(G_CALLBACK(TextureBrowser_expose), + &g_TextureBrowser); + + g_TextureBrowser.m_gl_widget.connect("button_press_event", G_CALLBACK(TextureBrowser_button_press), + &g_TextureBrowser); + g_TextureBrowser.m_gl_widget.connect("button_release_event", G_CALLBACK(TextureBrowser_button_release), + &g_TextureBrowser); + g_TextureBrowser.m_gl_widget.connect("motion_notify_event", G_CALLBACK(TextureBrowser_motion), + &g_TextureBrowser); + g_TextureBrowser.m_gl_widget.connect("scroll_event", G_CALLBACK(TextureBrowser_scroll), &g_TextureBrowser); + } + + // tag stuff + if (g_TextureBrowser.m_tags) { + { // fill tag GtkListStore + g_TextureBrowser.m_all_tags_list = ui::ListStore::from(gtk_list_store_new(N_COLUMNS, G_TYPE_STRING)); + auto 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 + auto menu_tags = ui::Menu(ui::New); + auto tags_item = TextureBrowser_constructTagsMenu(menu_tags); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(tags_item), menu_tags); + gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), tags_item); + } + { // Tag TreeView + g_TextureBrowser.m_scr_win_tags = ui::ScrolledWindow(ui::New); + 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(); + + auto selection = gtk_tree_view_get_selection(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), + g_TextureBrowser.m_treeViewTags); + g_TextureBrowser.m_treeViewTags.show(); + } + { // Texture/Tag notebook + TextureBrowser_constructTagNotebook(); + vbox.pack_start(g_TextureBrowser.m_tag_notebook, TRUE, TRUE, 0); + } + { // Tag search button + TextureBrowser_constructSearchButton(); + vbox.pack_end(g_TextureBrowser.m_search_button, FALSE, FALSE, 0); + } + auto frame_table = ui::Table(3, 3, FALSE); + { // Tag frame + + g_TextureBrowser.m_tag_frame = ui::Frame("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); + + table.attach(g_TextureBrowser.m_tag_frame, {1, 3, 2, 3}, {GTK_FILL, GTK_SHRINK}); + + frame_table.show(); + + g_TextureBrowser.m_tag_frame.add(frame_table); + } + { // assigned tag list + ui::Widget scrolled_win = ui::ScrolledWindow(ui::New); + 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 = ui::ListStore::from(gtk_list_store_new(N_COLUMNS, G_TYPE_STRING)); + + auto sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_assigned_store); + gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING); + + auto renderer = ui::CellRendererText(ui::New); + + g_TextureBrowser.m_assigned_tree = ui::TreeView( + ui::TreeModel::from(g_TextureBrowser.m_assigned_store._handle)); + g_TextureBrowser.m_assigned_store.unref(); + g_TextureBrowser.m_assigned_tree.connect("row-activated", (GCallback) TextureBrowser_removeTags, NULL); + gtk_tree_view_set_headers_visible(g_TextureBrowser.m_assigned_tree, FALSE); + + auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_assigned_tree); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + + auto column = ui::TreeViewColumn("", renderer, {{"text", TAG_COLUMN}}); + gtk_tree_view_append_column(g_TextureBrowser.m_assigned_tree, column); + g_TextureBrowser.m_assigned_tree.show(); + + scrolled_win.show(); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_win), g_TextureBrowser.m_assigned_tree); + + frame_table.attach(scrolled_win, {0, 1, 1, 3}, {GTK_FILL, GTK_FILL}); + } + { // available tag list + ui::Widget scrolled_win = ui::ScrolledWindow(ui::New); + 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 = ui::ListStore::from(gtk_list_store_new(N_COLUMNS, G_TYPE_STRING)); + auto sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_available_store); + gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING); + + auto renderer = ui::CellRendererText(ui::New); + + g_TextureBrowser.m_available_tree = ui::TreeView( + ui::TreeModel::from(g_TextureBrowser.m_available_store._handle)); + g_TextureBrowser.m_available_store.unref(); + g_TextureBrowser.m_available_tree.connect("row-activated", (GCallback) TextureBrowser_assignTags, NULL); + gtk_tree_view_set_headers_visible(g_TextureBrowser.m_available_tree, FALSE); + + auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + + auto column = ui::TreeViewColumn("", renderer, {{"text", TAG_COLUMN}}); + gtk_tree_view_append_column(g_TextureBrowser.m_available_tree, column); + g_TextureBrowser.m_available_tree.show(); + + scrolled_win.show(); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_win), g_TextureBrowser.m_available_tree); + + frame_table.attach(scrolled_win, {2, 3, 1, 3}, {GTK_FILL, GTK_FILL}); + } + { // tag arrow buttons + auto m_btn_left = ui::Button(ui::New); + auto m_btn_right = ui::Button(ui::New); + auto m_arrow_left = ui::Widget::from(gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_OUT)); + auto m_arrow_right = ui::Widget::from(gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_OUT)); + m_btn_left.add(m_arrow_left); + m_btn_right.add(m_arrow_right); + + // workaround. the size of the tag frame depends of the requested size of the arrow buttons. + m_arrow_left.dimensions(-1, 68); + m_arrow_right.dimensions(-1, 68); + + frame_table.attach(m_btn_left, {1, 2, 1, 2}, {GTK_SHRINK, GTK_EXPAND}); + frame_table.attach(m_btn_right, {1, 2, 2, 3}, {GTK_SHRINK, GTK_EXPAND}); + + m_btn_left.connect("clicked", G_CALLBACK(TextureBrowser_assignTags), NULL); + m_btn_right.connect("clicked", G_CALLBACK(TextureBrowser_removeTags), NULL); + + m_btn_left.show(); + m_btn_right.show(); + m_arrow_left.show(); + m_arrow_right.show(); + } + { // tag fram labels + ui::Widget m_lbl_assigned = ui::Label("Assigned"); + ui::Widget m_lbl_unassigned = ui::Label("Available"); + + frame_table.attach(m_lbl_assigned, {0, 1, 0, 1}, {GTK_EXPAND, GTK_SHRINK}); + frame_table.attach(m_lbl_unassigned, {2, 3, 0, 1}, {GTK_EXPAND, GTK_SHRINK}); + + m_lbl_assigned.show(); + m_lbl_unassigned.show(); + } + } else { // no tag support, show the texture tree only + vbox.pack_start(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); + + g_TextureBrowser.m_gl_widget.unref(); +} + +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(ui::TreeModel model, ui::TreePath 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; + 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 + auto selection = gtk_tree_view_get_selection(g_TextureBrowser.m_available_tree); + gtk_tree_selection_select_iter(selection, &iter); + + g_TextureBrowser.m_all_tags_list.append(TAG_COLUMN, tag.c_str()); + } +} + +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; + + auto selection = gtk_tree_view_get_selection(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(g_TextureBrowser.m_all_tags_list, &iterList) != 0; + + while (row) { + gtk_tree_model_get(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(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 { + ui::alert(g_TextureBrowser.m_parent, "Select a single tag for renaming."); + } +} + +void TextureBrowser_deleteTag() +{ + GSList *selected = NULL; + + auto selection = gtk_tree_view_get_selection(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 + auto result = ui::alert(g_TextureBrowser.m_parent, "Are you sure you want to delete the selected tag?", + "Delete Tag", ui::alert_type::YESNO, ui::alert_icon::Question); + + if (result == ui::alert_response::YES) { + GtkTreeIter iterSelected; + gchar *rowTag; + + gchar *tagSelected = (char *) selected->data; + + bool row = gtk_tree_model_get_iter_first(g_TextureBrowser.m_all_tags_list, &iterSelected) != 0; + + while (row) { + gtk_tree_model_get(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(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 { + ui::alert(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 TextureBrowser_RefreshShaders() +{ + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Shaders"); + GlobalShaderSystem().refresh(); + UpdateAllWindows(); + auto selection = gtk_tree_view_get_selection(GlobalTextureBrowser().m_treeViewTree); + GtkTreeModel *model = NULL; + GtkTreeIter iter; + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + gchar dirName[1024]; + + gchar *buffer; + gtk_tree_model_get(model, &iter, 0, &buffer, -1); + strcpy(dirName, buffer); + g_free(buffer); + if (!TextureBrowser_showWads()) { + strcat(dirName, "/"); + } + TextureBrowser_ShowDirectory(GlobalTextureBrowser(), dirName); + TextureBrowser_queueDraw(GlobalTextureBrowser()); + } +} + +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() +{ + auto result = ui::alert(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", ui::alert_type::YESNO, ui::alert_icon::Warning); + + if (result == ui::alert_response::YES) { + 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_FilterMissing() +{ + g_TextureBrowser_filterMissing ^= 1; + GlobalTextureBrowser().m_filternotex_item.update(); + TextureBrowser_activeShadersChanged(GlobalTextureBrowser()); + TextureBrowser_RefreshShaders(); +} + +void TextureBrowser_FilterFallback() +{ + g_TextureBrowser_filterFallback ^= 1; + GlobalTextureBrowser().m_hidenotex_item.update(); + TextureBrowser_activeShadersChanged(GlobalTextureBrowser()); + TextureBrowser_RefreshShaders(); +} + +void TextureBrowser_EnableAlpha() +{ + g_TextureBrowser_enableAlpha ^= 1; + GlobalTextureBrowser().m_enablealpha_item.update(); + TextureBrowser_activeShadersChanged(GlobalTextureBrowser()); +} + +void TextureBrowser_exportTitle(const Callback &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()); +} + +struct TextureScale { + static void Export(const TextureBrowser &self, const Callback &returnz) + { + switch (self.m_textureScale) { + case 10: + returnz(0); + break; + case 25: + returnz(1); + break; + case 50: + returnz(2); + break; + case 100: + returnz(3); + break; + case 200: + returnz(4); + break; + } + } + + static void Import(TextureBrowser &self, int value) + { + switch (value) { + case 0: + TextureBrowser_setScale(self, 10); + break; + case 1: + TextureBrowser_setScale(self, 25); + break; + case 2: + TextureBrowser_setScale(self, 50); + break; + case 3: + TextureBrowser_setScale(self, 100); + break; + case 4: + TextureBrowser_setScale(self, 200); + break; + } + } +}; + +struct UniformTextureSize { + static void Export(const TextureBrowser &self, const Callback &returnz) + { + returnz(g_TextureBrowser.m_uniformTextureSize); + } + + static void Import(TextureBrowser &self, int value) + { + if (value > 16) { + TextureBrowser_setUniformSize(self, value); + } + } +}; + +void TextureBrowser_constructPreferences(PreferencesPage &page) +{ + page.appendCheckBox( + "", "Texture scrollbar", + make_property(GlobalTextureBrowser()) + ); + { + const char *texture_scale[] = {"10%", "25%", "50%", "100%", "200%"}; + page.appendCombo( + "Texture Thumbnail Scale", + STRING_ARRAY_RANGE(texture_scale), + make_property(GlobalTextureBrowser()) + ); + } + page.appendSpinner( + "Texture Thumbnail Size", + GlobalTextureBrowser().m_uniformTextureSize, + GlobalTextureBrowser().m_uniformTextureSize, + 16, 8192 + ); + 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(makeCallbackF(TextureBrowser_constructPage)); +} + + +#include "preferencesystem.h" +#include "stringio.h" + + +void TextureClipboard_textureSelected(const char *shader); + +void TextureBrowser_Construct() +{ + GlobalCommands_insert("ShaderInfo", makeCallbackF(TextureBrowser_shaderInfo)); + GlobalCommands_insert("ShowUntagged", makeCallbackF(TextureBrowser_showUntagged)); + GlobalCommands_insert("AddTag", makeCallbackF(TextureBrowser_addTag)); + GlobalCommands_insert("RenameTag", makeCallbackF(TextureBrowser_renameTag)); + GlobalCommands_insert("DeleteTag", makeCallbackF(TextureBrowser_deleteTag)); + GlobalCommands_insert("CopyTag", makeCallbackF(TextureBrowser_copyTag)); + GlobalCommands_insert("PasteTag", makeCallbackF(TextureBrowser_pasteTag)); + GlobalCommands_insert("RefreshShaders", makeCallbackF(VFS_Refresh)); + GlobalToggles_insert("ShowInUse", makeCallbackF(TextureBrowser_ToggleHideUnused), + ToggleItem::AddCallbackCaller(g_TextureBrowser.m_hideunused_item), Accelerator('U')); + GlobalCommands_insert("ShowAllTextures", makeCallbackF(TextureBrowser_showAll), + Accelerator('A', (GdkModifierType) GDK_CONTROL_MASK)); + GlobalCommands_insert("ToggleTextures", makeCallbackF(TextureBrowser_toggleShow), Accelerator('T')); + GlobalToggles_insert("ToggleShowShaders", makeCallbackF(TextureBrowser_ToggleShowShaders), + ToggleItem::AddCallbackCaller(g_TextureBrowser.m_showshaders_item)); + GlobalToggles_insert("ToggleShowShaderlistOnly", makeCallbackF(TextureBrowser_ToggleShowShaderListOnly), + ToggleItem::AddCallbackCaller(g_TextureBrowser.m_showshaderlistonly_item)); + GlobalToggles_insert("FixedSize", makeCallbackF(TextureBrowser_FixedSize), + ToggleItem::AddCallbackCaller(g_TextureBrowser.m_fixedsize_item)); + GlobalToggles_insert("FilterMissing", makeCallbackF(TextureBrowser_FilterMissing), + ToggleItem::AddCallbackCaller(g_TextureBrowser.m_filternotex_item)); + GlobalToggles_insert("FilterFallback", makeCallbackF(TextureBrowser_FilterFallback), + ToggleItem::AddCallbackCaller(g_TextureBrowser.m_hidenotex_item)); + GlobalToggles_insert("EnableAlpha", makeCallbackF(TextureBrowser_EnableAlpha), + ToggleItem::AddCallbackCaller(g_TextureBrowser.m_enablealpha_item)); + + GlobalPreferenceSystem().registerPreference("TextureScale", make_property_string(g_TextureBrowser)); + GlobalPreferenceSystem().registerPreference("UniformTextureSize", + make_property_string(g_TextureBrowser)); + GlobalPreferenceSystem().registerPreference("TextureScrollbar", make_property_string( + GlobalTextureBrowser())); + GlobalPreferenceSystem().registerPreference("ShowShaders", + make_property_string(GlobalTextureBrowser().m_showShaders)); + GlobalPreferenceSystem().registerPreference("ShowShaderlistOnly", + make_property_string(g_TextureBrowser_shaderlistOnly)); + GlobalPreferenceSystem().registerPreference("FixedSize", make_property_string(g_TextureBrowser_fixedSize)); + GlobalPreferenceSystem().registerPreference("FilterMissing", make_property_string(g_TextureBrowser_filterMissing)); + GlobalPreferenceSystem().registerPreference("EnableAlpha", make_property_string(g_TextureBrowser_enableAlpha)); + GlobalPreferenceSystem().registerPreference("LoadShaders", make_property_string( + reinterpret_cast( GlobalTextureBrowser().m_startupShaders ))); + GlobalPreferenceSystem().registerPreference("WheelMouseInc", make_property_string( + GlobalTextureBrowser().m_mouseWheelScrollIncrement)); + GlobalPreferenceSystem().registerPreference("SI_Colors0", + make_property_string(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 0000000..5c5e5d8 --- /dev/null +++ b/radiant/texwindow.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 +#include "property.h" +#include "math/vector.h" +#include "generic/callback.h" +#include "signal/signalfwd.h" +#include "xml/xmltextags.h" + +class TextureBrowser; + +TextureBrowser &GlobalTextureBrowser(); + +ui::Widget TextureBrowser_constructWindow(ui::Window 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(); + +extern ui::Widget g_page_textures; + +void TextureBrowser_exportTitle(const Callback &importer); + +typedef FreeCaller &), TextureBrowser_exportTitle> 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); + +void TextureBrowser_RefreshShaders(); + +#endif diff --git a/radiant/timer.cpp b/radiant/timer.cpp new file mode 100644 index 0000000..6978510 --- /dev/null +++ b/radiant/timer.cpp @@ -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 + */ + +#include "timer.h" +#include "globaldefs.h" + + +#if GDEF_OS_WINDOWS + +#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 GDEF_OS_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 0000000..59d171d --- /dev/null +++ b/radiant/timer.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 0000000..c0463be --- /dev/null +++ b/radiant/treemodel.cpp @@ -0,0 +1,1488 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 "globaldefs.h" + +#include "debugging/debugging.h" + +#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( ui::TreeModel 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; + +ui::TreeStore 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( model, iter, parent ); + success != FALSE; + success = gtk_tree_model_iter_next( model, iter ) ) + { + scene::Node* current; + gtk_tree_model_get_pointer( 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( ui::TreeModel 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( ui::TreeModel 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( ui::TreeModel tree_model, GtkTreeIter* iter, ui::TreePath 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 ui::TreePath graph_tree_model_get_path( ui::TreeModel 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 ); + + auto path = ui::TreePath(); + + 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( ui::TreeModel 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( ui::TreeModel 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( ui::TreeModel 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( ui::TreeModel 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( ui::TreeModel 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( ui::TreeModel 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( ui::TreeModel 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, ui::TreePath path ){ +#if GDEF_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, ui::TreePath path ){ + GtkTreeIter iter; + + if ( gtk_tree_model_get_iter( 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, ui::TreePath path, GtkSelectionData *selection_data ){ + if ( gtk_tree_set_row_drag_data( selection_data, 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, ui::TreePath dest, GtkSelectionData *selection_data ){ + auto 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, ui::TreePath 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 == 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 ) { + auto tmp = gtk_tree_path_copy( dest_path ); + gtk_tree_path_up( tmp ); + + GtkTreeIter iter; + retval = gtk_tree_model_get_iter( 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 ){ + // Reference cannot be bound to dereferenced null pointer in well-defined + // C++ code, and Clang will assume that comparison below always evaluates + // to true, resulting in a segmentation fault. Use a dirty hack to force + // Clang to check those "bad" references for null nonetheless. + volatile intptr_t n = (intptr_t)&node; + + if ( n != 0 ) { + Nameable* nameable = Node_getNameable( node ); + if ( nameable != 0 ) { + nameable->attach( callback ); + } + } +} +void node_detach_name_changed_callback( scene::Node& node, const NameCallback& callback ){ + volatile intptr_t n = (intptr_t)&node; // see the comment on line 650 + + if ( n != 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 ); + + auto tree_path = graph_tree_model_get_path( 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( 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 ); + + auto tree_path = graph_tree_model_get_path( model, &iter ); + + gtk_tree_model_row_deleted( 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(), ConstReferenceCaller( instance ) ); +} + +void graph_tree_model_erase( GraphTreeModel* model, const scene::Instance& instance ){ + node_detach_name_changed_callback( instance.path().top(), ConstReferenceCaller( 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; +}; + +static GtkTreeModelFlags graph_tree_model_get_flags(ui::TreeModel tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static gint graph_tree_model_get_n_columns(ui::TreeModel tree_model) +{ + ASSERT_MESSAGE(tree_model, "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(ui::TreeModel tree_model, gint index) +{ + ASSERT_MESSAGE(tree_model, "RUNTIME ERROR"); + //GraphTreeModel *graph_tree_model = (GraphTreeModel *) tree_model; + + return G_TYPE_POINTER; +} + +static gboolean graph_tree_model_get_iter(GraphTreeModel *tree_model, GtkTreeIter *iter, ui::TreePath 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 = 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(GTK_TREE_MODEL(tree_model), iter, parent, indices[i])) { + return FALSE; + } + tmp = *iter; + parent = &tmp; + } + + return TRUE; +} + +static ui::TreePath graph_tree_model_get_path(GraphTreeModel *tree_model, GtkTreeIter *iter) +{ + ASSERT_MESSAGE(tree_model, "RUNTIME ERROR"); + GraphTreeNode *graph = tree_model->m_graph; + + auto path = ui::TreePath(ui::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(ui::TreeModel tree_model, GtkTreeIter *iter, gint column, GValue *value) +{ + ASSERT_MESSAGE(tree_model, "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(ui::TreeModel tree_model, GtkTreeIter *iter) +{ + ASSERT_MESSAGE(tree_model, "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(GraphTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode &node = (parent == 0) ? *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(ui::TreeModel tree_model, GtkTreeIter *iter) +{ + ASSERT_MESSAGE(tree_model, "RUNTIME ERROR"); + GraphTreeNode &node = *(*graph_iterator_read_tree_iter(iter)).second; + return !node.empty(); +} + +static gint graph_tree_model_iter_n_children(GraphTreeModel *tree_model, GtkTreeIter *parent) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode &node = (parent == 0) ? *tree_model->m_graph : *(*graph_iterator_read_tree_iter(parent)).second; + return static_cast( node.size()); +} + +static gboolean +graph_tree_model_iter_nth_child(GraphTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode &node = (parent == 0) ? *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(GraphTreeModel *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 != 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) +{ + auto graph_tree_model = reinterpret_cast(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 = reinterpret_cast(graph_tree_model_get_flags); + iface->get_n_columns = reinterpret_cast(graph_tree_model_get_n_columns); + iface->get_column_type = reinterpret_cast(graph_tree_model_get_column_type); + iface->get_iter = reinterpret_cast(graph_tree_model_get_iter); + iface->get_path = reinterpret_cast(graph_tree_model_get_path); + iface->get_value = reinterpret_cast(graph_tree_model_get_value); + iface->iter_next = reinterpret_cast(graph_tree_model_iter_next); + iface->iter_children = reinterpret_cast(graph_tree_model_iter_children); + iface->iter_has_child = reinterpret_cast(graph_tree_model_iter_has_child); + iface->iter_n_children = reinterpret_cast(graph_tree_model_iter_n_children); + iface->iter_nth_child = reinterpret_cast(graph_tree_model_iter_nth_child); + iface->iter_parent = reinterpret_cast(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() +{ + auto graph_tree_model = reinterpret_cast(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); + + auto tree_path = graph_tree_model_get_path(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); + + auto tree_path = graph_tree_model_get_path(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); + + auto tree_path = graph_tree_model_get_path(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) +{ + volatile intptr_t n = (intptr_t) &node; // see the comment on line 650 + if (n == 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) +{ + volatile intptr_t n = (intptr_t) &node; // see the comment on line 650 + if (n != 0) { + Nameable *nameable = Node_getNameable(node); + if (nameable != 0) { + nameable->attach(callback); + } + } +} + +void node_detach_name_changed_callback(scene::Node &node, const NameCallback &callback) +{ + volatile intptr_t n = (intptr_t) &node; // see the comment on line 650 + if (n != 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 Callback &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 Callback &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, ReferenceCaller(*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, ReferenceCaller(*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(), ConstReferenceCaller(instance)); +} + +void graph_tree_model_erase(GraphTreeModel *model, const scene::Instance &instance) +{ + node_detach_name_changed_callback(instance.path().top(), ConstReferenceCaller(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(); + + auto 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; + auto path = ui::TreePath( "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; + auto path = ui::TreePath( "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 ); + + auto 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 ); + + auto 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 0000000..a533f3e --- /dev/null +++ b/radiant/treemodel.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_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 0000000..dd92e8a --- /dev/null +++ b/radiant/undo.cpp @@ -0,0 +1,601 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 { + UINT_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; + PropertyImpl::Import(levels, value); + undo.setLevels(levels); +} + +typedef ReferenceCaller UndoLevelsImportStringCaller; + +void UndoLevels_exportString(const RadiantUndoSystem &undo, const Callback &importer) +{ + PropertyImpl::Export(static_cast( undo.getLevels()), importer); +} + +typedef ConstReferenceCaller &), UndoLevels_exportString> UndoLevelsExportStringCaller; + +#include "generic/callback.h" + +struct UndoLevels { + static void Export(const RadiantUndoSystem &self, const Callback &returnz) + { + returnz(static_cast(self.getLevels())); + } + + static void Import(RadiantUndoSystem &self, int value) + { + self.setLevels(value); + } +}; + +void Undo_constructPreferences(RadiantUndoSystem &undo, PreferencesPage &page) +{ + page.appendSpinner("Undo Queue Size", 64, 0, 1024, make_property(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( + ReferenceCaller(undo)); +} + +class UndoSystemDependencies : public GlobalPreferenceSystemModuleRef { +}; + +class UndoSystemAPI { + RadiantUndoSystem m_undosystem; +public: + typedef UndoSystem Type; + + STRING_CONSTANT(Name, "*"); + + UndoSystemAPI() + { + GlobalPreferenceSystem().registerPreference("UndoLevels", make_property_string(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 0000000..89e6337 --- /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 0000000..034fc45 --- /dev/null +++ b/radiant/url.cpp @@ -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 + */ + +#include "url.h" +#include "globaldefs.h" + +#include "mainframe.h" +#include "gtkutil/messagebox.h" + +#if GDEF_OS_WINDOWS +#include +#include +#include +bool open_url( const char* url ){ + return ShellExecute( (HWND)GDK_WINDOW_HWND( gtk_widget_get_window( MainFrame_getWindow() ) ), "open", url, 0, 0, SW_SHOW ) > (HINSTANCE)32; +} +#endif + +#if GDEF_OS_LINUX || GDEF_OS_BSD + +#include + +bool open_url(const char *url) +{ + char command[2 * PATH_MAX]; + snprintf(command, sizeof(command), + "xdg-open \"%s\" &", url); + return system(command) == 0; +} + +#endif + +#if GDEF_OS_MACOS +#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)) { + ui::alert(MainFrame_getWindow(), "Failed to launch browser!"); + } +} diff --git a/radiant/url.h b/radiant/url.h new file mode 100644 index 0000000..829b039 --- /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 0000000..7e6726a --- /dev/null +++ b/radiant/view.cpp @@ -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 + */ + +#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 0000000..7b9ba48 --- /dev/null +++ b/radiant/view.h @@ -0,0 +1,223 @@ +/* + Copyright (C) 2001-2006, William Joseph. + All Rights Reserved. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 "globaldefs.h" +#include "cullable.h" +#include "math/frustum.h" + + +#if GDEF_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 0000000..2803ce9 --- /dev/null +++ b/radiant/watchbsp.cpp @@ -0,0 +1,819 @@ +/* + 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 "globaldefs.h" + +#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) +{ + ui::CheckButton monitorbsp = page.appendCheckBox("", "Enable Build Process Monitoring", g_WatchBSP_Enabled); + ui::CheckButton leakstop = page.appendCheckBox("", "Stop Compilation on Leak", g_WatchBSP_LeakStop); + ui::CheckButton runengine = page.appendCheckBox("", "Run Engine After Compile", g_WatchBSP_RunQuake); + ui::CheckButton 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(makeCallbackF(Build_constructPage)); +} + +#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", make_property_string(g_WatchBSP_Enabled)); + GlobalPreferenceSystem().registerPreference("RunQuake2Run", make_property_string(g_WatchBSP_RunQuake)); + GlobalPreferenceSystem().registerPreference("LeakStop", make_property_string(g_WatchBSP_LeakStop)); + GlobalPreferenceSystem().registerPreference("SleepMode", make_property_string(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); +#if GDEF_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 << 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) { + g_source_remove(s_routine_id); + } +} + +bool CWatchBSP::SetupListening() +{ +#if GDEF_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; + ui::alert(MainFrame_getWindow(), msg, "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error); + 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, false)) { + 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(); + ui::alert(MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, + ui::alert_icon::Error); + return; + } + // re-initialise the debug window + if (m_iCurrentStep == 0) { + g_DbgDlg.Init(); + } + } + m_eState = EBeginStep; + s_routine_id = g_timeout_add(25, watchbsp_routine, this); +} + + +#if GDEF_OS_WINDOWS +const char *ENGINE_ATTRIBUTE = "engine_win32"; +const char *MP_ENGINE_ATTRIBUTE = "mp_engine_win32"; +#elif GDEF_OS_LINUX || GDEF_OS_BSD +const char *ENGINE_ATTRIBUTE = "engine_linux"; +const char *MP_ENGINE_ATTRIBUTE = "mp_engine_linux"; +#elif GDEF_OS_MACOS +const char *ENGINE_ATTRIBUTE = "engine_macos"; +const char *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) { + ui::alert(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", ui::alert_type::OK); + EndMonitoringLoop(); +#if 0 + if ( m_bBSPPlugin ) { + // status == 1 : didn't get the connection + g_BSPFrontendTable.m_pfnEndListen( 1 ); + } +#endif + return; + } +#if GDEF_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: { +#if GDEF_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, false)) { + StringOutputStream msg; + msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str(); + globalOutputStream() << msg.c_str(); + ui::alert(MainFrame_getWindow(), msg.c_str(), "Build monitoring", ui::alert_type::OK, + ui::alert_icon::Error); + } + } + 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 (ui::alert(MainFrame_getWindow(), + "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?", + "Build process monitoring", ui::alert_type::YESNO) == ui::alert_response::YES) { + // 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 0000000..b8f4345 --- /dev/null +++ b/radiant/watchbsp.h @@ -0,0 +1,45 @@ +/* + 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 0000000..d027f52 --- /dev/null +++ b/radiant/winding.cpp @@ -0,0 +1,329 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public 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 < winding.numpoints; ++i) { + ASSERT_MESSAGE(winding[i].adjacent != c_brush_maxFaces, "edge connectivity data is invalid"); + if (winding[i].adjacent == face) { + return i; + } + } + return c_brush_maxFaces; +} + +std::size_t Winding_Opposite(const Winding &winding, const std::size_t index, const std::size_t other) +{ + ASSERT_MESSAGE(index < winding.numpoints && other < winding.numpoints, "Winding_Opposite: index out of range"); + + double dist_best = 0; + std::size_t index_best = c_brush_maxFaces; + + Ray edge(ray_for_points(winding[index].vertex, winding[other].vertex)); + + for (std::size_t i = 0; i < winding.numpoints; ++i) { + if (i == index || i == other) { + continue; + } + + double dist_squared = ray_squared_distance_to_point(edge, winding[i].vertex); + + if (dist_squared > 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 ¢roid) +{ + 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 0000000..85fdd22 --- /dev/null +++ b/radiant/winding.h @@ -0,0 +1,329 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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 ¢roid); + + +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 0000000..9dc75a5 --- /dev/null +++ b/radiant/windowobservers.cpp @@ -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 + */ + +#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(ui::Widget widget, GdkEventKey *event, WindowObservers &observers) +{ + switch (event->keyval) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + //globalOutputStream() << "Alt PRESSED\n"; + WindowObservers_OnModifierDown(observers, c_modifierAlt); + break; + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + //globalOutputStream() << "Shift PRESSED\n"; + WindowObservers_OnModifierDown(observers, c_modifierShift); + break; + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + //globalOutputStream() << "Control PRESSED\n"; + WindowObservers_OnModifierDown(observers, c_modifierControl); + break; + } + return FALSE; +} + +gboolean selection_modifier_key_release(ui::Widget widget, GdkEventKey *event, WindowObservers &observers) +{ + switch (event->keyval) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + //globalOutputStream() << "Alt RELEASED\n"; + WindowObservers_OnModifierUp(observers, c_modifierAlt); + break; + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + //globalOutputStream() << "Shift RELEASED\n"; + WindowObservers_OnModifierUp(observers, c_modifierShift); + break; + case GDK_KEY_Control_L: + case GDK_KEY_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(ui::Widget 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(ui::Widget widget, GdkEventButton *event, WindowObservers *observers) +{ + if (event->type == GDK_BUTTON_RELEASE) { + WindowObservers_UpdateModifiers(*observers, modifiers_for_state(event->state)); + } + return FALSE; +} + +gboolean modifiers_motion(ui::Widget 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(ui::Window window) +{ + window.connect("key_press_event", G_CALLBACK(selection_modifier_key_press), &g_window_observers); + window.connect("key_release_event", G_CALLBACK(selection_modifier_key_release), &g_window_observers); +} + +void GlobalWindowObservers_connectWidget(ui::Widget widget) +{ + widget.connect("button_press_event", G_CALLBACK(modifiers_button_press), &g_window_observers); + widget.connect("button_release_event", G_CALLBACK(modifiers_button_release), &g_window_observers); + widget.connect("motion_notify_event", G_CALLBACK(modifiers_motion), &g_window_observers); +} + +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; +} diff --git a/radiant/windowobservers.h b/radiant/windowobservers.h new file mode 100644 index 0000000..a39c934 --- /dev/null +++ b/radiant/windowobservers.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_WINDOWOBSERVERS_H ) +#define INCLUDED_WINDOWOBSERVERS_H + +#include "windowobserver.h" + +#include + +#include "math/vector.h" + +class WindowObserver; + +void GlobalWindowObservers_add(WindowObserver *observer); + +void GlobalWindowObservers_connectWidget(ui::Widget widget); + +void GlobalWindowObservers_connectTopLevel(ui::Window 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; +} + +ModifierFlags modifiers_for_state(unsigned int state); +ModifierFlags modifiers_for_state_cam(unsigned int state); + +inline WindowVector WindowVector_forDouble(double x, double y) +{ + return WindowVector(static_cast( x ), static_cast( y )); +} + +#endif diff --git a/radiant/worldspawn.ico b/radiant/worldspawn.ico new file mode 100644 index 0000000000000000000000000000000000000000..592a1844e17eb4d77cb14aec01290f332578b27f GIT binary patch literal 5430 zcmbuD2UOHozQ@Oyyd-b4hBSLYO`?e!NS?7ojerGf6j4NqAXbW^0@9HV(uUq~7&_8> zRX`~!3Zfubh+siYBIv4-81(ZlX5E*Z&C5y7-g7Q@_|N_I@2|{XA{ioiMWU!E5vQUg zO+h00SR#=qh;#7&U9URbj$J#XT~ z2;Z@xPji{_@A~1bsef0vEq85h3UjANVCimU0gqeGQSIl({j;ZeRC`Q3E21MijlMId z=q|~lHSI7r$_r`FkkekAOCis5XBx z=KX)e!>tq_zx$P0+^fjpc)&q=8yo0Ijis-tk)N-3(_Ng!wW1vA<&ivUsG+x^n(m@Z zN`!sKp0wr zwpF}ec7(?Q-!}@gU)bE77RT{Wf3B7n)8E-ndwwRjO7rP!ZszfYb9{gLBs~@RT+B-5 zY(hB4q5_Dr-A`_)FD_eFQ7CmM#nlRXt;JMHJ-C&hMy20DB208K)>LEem*W&)>ToMy zuk1!?CVef9lm@$t`wmnD`UsgB-0Qr`<)UnkCd=qO*GylB$hrFuczC^w%XKFxiwGp$ z!Tnq@Qi^c zPq@=`n#*PR$XyO#r@f2qPFqu=cnAPE~QfV&fip@oDd(*XD84YA5EmSDN*~kkZ8M2;8cdys!wrWKMxn} zg{Xb;iQ&DxL|LP|CB4NRc%q;T#|=!WVa6S+ct7OBc1-Mm$@x4RuvIU;z0{yoNVZ5Jj30# z^9($C#H}lrxp(yvJpvC+WyR!q+Ou!@92~c-CE3M-2%9}b3q6b%PQgLrE2<*>u~7XO zzs>46E?44^{!+}fRjY1?T2r}8lL9j>@~n4oT-1-Q%2KjK4kzw5zzr`}7_OrvDwz0v zn}z%janqcK|E9%+7^<^p-9l~#SyQ@o5xI_gC=oku=QsFk%)w^CL~H~fR}B@id_>%n zj}aDJK}BH=hg@9n-nof*$GzBTsbI1EODvX5W9Qt-n0_@Gvjvl}Su!1GJq?jZUSzpi zkg(4H8?}kZ%yh6{@fkrzE7-kuCf|oxaoXCDqoPLF1h{h{Baw#Ka0=}AaJ#&eYh{I` zMC9P$lucyxQBn_Q5EB-T$4)I`M7(U)eTA{A5(n1I#b)*V7rHpEQNu-d6|qj{xNlTP zx^+EX>%Yclqbl)&CfH~-hqT4rb+fqZV?wRKbBCyt=X2A9-^lrAFYdKq|$_ zAp&oYG~!}%@j4iS(e`clT3O;D_&9G=W2^cCc5d5(!OZdO65Dvzhc99lEo!dy5+xi( zPI|4IC$PLq$W_5@-E1OE*I}l)h@M!Fs!I``oC$U2PGdc%jwDeTEk*8ZP4JHO#Ck;& z;^t3LR0?HDIW%RLkSOeT-)l_ZHVtA#ZdtBZ!PR;%ChA`h?_f^c{>}Jp*TQDyJZzRK z;k9ugwrl6&Yq*lAJp$uaM#%T*vVHB+ss}~!vP%I@oELSpMAVm*JqF|*+()bEGx581 zX(~O+$(&4%CdjEuNECJF4iA3lr79zX3=cb;1(pI#4Dq$Mr#3a7Qt2VGh2Qq6OvPK+ zYPV_@u_Et+_HM^cV>TxR7SG0p;BB}B?NxJR{n@^T*ADwp=emy*0{hYW%gM7dA;WSz z?%JwU$H!2cm&4Jh2u^2bbMMA&PL);Cb@~h`UI&Twb|Y2P-f~}8>N3*s(OZMB!5Yk# z&&5t%8K<@L$oIBEF8Xi0s70+w(G+_+;4boT>B5-;cmWFE$$bYdin>;2VL-a+deZjj zktpuA5oEGC3t3uDYwr{bxh zij&YidZ!K%yEJgsT}GJE8XCd_IVpTB7r9`st3EJh#>f|a_+G4|>|#t14W4#X+wY`W z^p6LkzkGN06pa~4RLdf0tvxRMAH}h(G_JO^a81kr7pp5d66{O3*=E{L)l-v}Kvklg zD2D?$ubzj)vQH@xIht&1Mq^|E<=zfN?$#pKcn#Y&%zx2;276F{NtojGAX{qe_EPD2 zQ1pK{YQm(nWTuN=V9zZvf80FPh_{Hznc^aDU2LNwC!4O5wN#|1kSA(Ywdf6HK|W-8 zI8dJyPm;GY89uJuZ#qv?Toe^jPg3?7lD1zD&+Y42{>>c4LD?@?Uv_}s)hH1gTYCx} zY^V;Y2>#k`d2 zVo$!HEee-%rLt1gZz~e_>WjSACU)0atn}CYX8st|fzvSx5Aw^JThr<|>K9Lzg9nlO zEs1lqBgVp%XiHOaeBA}sH^1O}T$V^#oSgEQIC6Y^@YrF%AuUxrw3pzmw_MaFZKMXP zM6XpNdAB|tNpgw>u48ttYhJbF^M9MaUg%I8uhf%O+Shudk%pui%3>Mx)`C-aEStWhvQyQsHzeIKZEh{O z4Rmy-GDz_C4#eL|D&{*qfPbG zEJVIV4f*ypWSAL|Z*JI}XuA0i?S3iy<;kfSg~vw{1Ny4#`A1Vb*D5U;Tn#{lv3s!OkIdCXT$s{a}+(z-XfsFV&Ct^`=zWuI-k~O zDfSnK%kF1{KfjTYLuY0gZIRJ51VxEiyMSYnB{YO3b0sT_mc#_Qj>zduj(OgBI9OKd zVy^f{y#LB~90^qDZ_F_4Kb|l9E?ZW0CnbpMNfF#W63fH#WPV7Ethz3j$}R;t7(Pw( z{?qnSJ#_F_Is+2P!hVTl!hMP4?JkMr^=gS^=-_r1uMY;d>l!8V#k{bYJe^LVYbaJFl?%%U9VXnI_pf`yIhiEQeo? zU;kY+<4qpk`+?i-wLIwUCBj^rhfi*!J$n@MrVK-4mNH*|GW3_129Cd`lt^ss{aChi z18b)Koe%?cd^N{nr2HnIT9hzFWfwp8eoyy>(`;F$jN;fKNG$%3US;-h&bC}dbH)cO z`{X^AO@15w&xdi`bSxhkS2AfsA|E-)?vmZC+INik(_dri`$HN1?hsZB z+jZuTL2J$!LUt&#OLZd4J{!U2c@t>AbQjn9M*f!Q&*(5)-gDlZ&GbjC> z+2h_saoA7{mXBo1($T^n3f%8*6TTRask$Oxnb;upT)~i4D;W~9lht33X0h@JEQGIS zjT^?7V}~*QGbIePMsnlZCQR3k!NW|A$jmiZme$}<5{d4DvFNLeM|0+AWty2 zP8-hGAAZ36Y2%nW;lE$Rz+vx9tn|jwdAW=K@-h?t7=@wA7#6H~AKiIlS@rn{*3KEl z%Fjo#aQgcwO;BKphUUNGueW+QoA$2Yb@#Ij5AWmkaxdPCn2XNWqu8c04kNMG2@D%8 zoPhGgxA|z~TYT}+JO82|M3G0DlnJT#dDUJA^QHpc$lHZt%sxVE3WV$t=*=BNVO0a` z)n_op$on_6|Ae8$VB<)#YKq7?*T}0@s%V7T(IfK7FWCz-S6@!GbueevZ{@zE{q^bR zHyNj#6s+@Q*yS~Gspl>`cZ~X7nf((alCIY!lIo!niL + +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 ~ISAXHandler() = default; + + 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 0000000..ef261bc --- /dev/null +++ b/radiant/xywindow.cpp @@ -0,0 +1,2998 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General 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 + +#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), + /* all very performance intensive */ + show_names(false), + show_coordinates(false), + show_angles(false), + show_outline(false), + show_axis(false), + d_show_work(false), + + show_blocks(true), + + m_bCamXYUpdate(true), + m_bChaseMouse(true), + m_bSizePaint(true) + { + } +}; + +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::INVALID: + break; + 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; + +ui::Menu XYWnd::m_mnuDrop(ui::null); +ui::Menu XYWnd::m_mnuDropSingle(ui::null); +ui::Menu XYWnd::m_mnuDropMultiple(ui::null); + +// 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 = ui::file_dialog( 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 (self.GetWidget().visible()) { + self.m_XORRectangle.set(rectangle_from_area(area.min, area.max, self.Width(), self.Height())); + } +} + +gboolean xywnd_button_press(ui::Widget 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(ui::Widget 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; +} + +gboolean xywnd_focus_in(ui::Widget widget, GdkEventFocus *event, XYWnd *xywnd) +{ + if (event->type == GDK_FOCUS_CHANGE) { + if (event->in) { + g_pParentWnd->SetActiveXY(xywnd); + } + } + 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(ui::Widget 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(ui::Widget 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(ui::Widget 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(ui::null), + 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 = ui::Menu(ui::null); + m_mnuDropSingle = ui::Menu(ui::null); + m_mnuDropMultiple = ui::Menu(ui::null); + + GlobalWindowObservers_add(m_window_observer); + GlobalWindowObservers_connectWidget(m_gl_widget); + + m_window_observer->setRectangleDrawCallback(ReferenceCaller(*this)); + m_window_observer->setView(m_view); + + g_object_ref(m_gl_widget._handle); + + 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_can_focus(m_gl_widget, true); + + m_sizeHandler = m_gl_widget.connect("size_allocate", G_CALLBACK(xywnd_size_allocate), this); + m_exposeHandler = m_gl_widget.on_render(G_CALLBACK(xywnd_expose), this); + + m_gl_widget.connect("button_press_event", G_CALLBACK(xywnd_button_press), this); + m_gl_widget.connect("button_release_event", G_CALLBACK(xywnd_button_release), this); + m_gl_widget.connect("focus_in_event", G_CALLBACK(xywnd_focus_in), this); + m_gl_widget.connect("motion_notify_event", G_CALLBACK(DeferredMotion::gtk_motion), &m_deferred_motion); + + m_gl_widget.connect("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) { + m_mnuDrop.destroy(); + m_mnuDrop = ui::Menu(ui::null); + } + + if (m_mnuDropSingle) { + m_mnuDropSingle.destroy(); + m_mnuDropSingle = ui::Menu(ui::null); + } + + if (m_mnuDropMultiple) { + m_mnuDropMultiple.destroy(); + m_mnuDropMultiple = ui::Menu(ui::null); + } + + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_sizeHandler); + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_exposeHandler); + + m_gl_widget.unref(); + + 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, GetSnapGridSize()); + 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(gtk_widget_get_window(m_gl_widget), cursor); + gdk_cursor_unref(cursor); + } else { + gdk_window_set_cursor(gtk_widget_get_window(m_gl_widget), 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], GetSnapGridSize()); + maxs[nDim] = float_snapped(Select_getWorkZone().d_work_max[nDim], GetSnapGridSize()); + + 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(ui::Widget item) +{ + scene::Node *world_node = Map_FindWorldspawn(g_map); + const char *entity_name = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(item)))); + + if (!(world_node && string_equal(entity_name, "worldspawn"))) { + g_pParentWnd->ActiveXY()->OnEntityCreate(entity_name); + } else { + GlobalRadiant().m_pfnMessageBox(MainFrame_getWindow(), "There's already a worldspawn in your map!" + "", + "Info", + eMB_OK, + eMB_ICONDEFAULT); + } +} + +void EntityClassMenu_addItem(ui::Menu menu, const char *name) +{ + auto item = ui::MenuItem(name); + item.connect("activate", G_CALLBACK(entitycreate_activated), item); + item.show(); + 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(ui::Menu 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) + { + auto item = ui::MenuItem(name.c_str()); + item.show(); + m_stack.back().first.add(item); + + auto submenu = ui::Menu(ui::New); + gtk_menu_item_set_submenu(item, 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; + } + + /* first time, init */ + if (!m_mnuDrop) { + auto menu1 = m_mnuDrop = ui::Menu(ui::New); + EntityClassMenuInserter inserter(menu1); + GlobalEntityClassManager().forEachPoint(inserter); + } + if (!m_mnuDropSingle) { + auto menu2 = m_mnuDropSingle = ui::Menu(ui::New); + create_menu_item_with_mnemonic(menu2, "Make detail", "MakeDetail"); + create_menu_item_with_mnemonic(menu2, "Make structural", "MakeStructural"); + create_menu_item_with_mnemonic(menu2, "Snap selection to _grid", "SnapToGrid"); + menu_separator(menu2); + create_menu_item_with_mnemonic(menu2, "Arbitrary rotation...", "ArbitraryRotation"); + create_menu_item_with_mnemonic(menu2, "Arbitrary scale...", "ArbitraryScale"); + menu_separator(menu2); + create_menu_item_with_mnemonic(menu2, "_To Worldspawn", "UngroupSelection"); + + } + if (!m_mnuDropMultiple) { + auto menu3 = m_mnuDropMultiple = ui::Menu(ui::New); + create_menu_item_with_mnemonic(menu3, "Make detail", "MakeDetail"); + create_menu_item_with_mnemonic(menu3, "Make structural", "MakeStructural"); + create_menu_item_with_mnemonic(menu3, "Snap selection to _grid", "SnapToGrid"); + menu_separator(menu3); + create_menu_item_with_mnemonic(menu3, "Arbitrary rotation...", "ArbitraryRotation"); + create_menu_item_with_mnemonic(menu3, "Arbitrary scale...", "ArbitraryScale"); + menu_separator(menu3); + create_menu_item_with_mnemonic(menu3, "_To Worldspawn", "UngroupSelection"); + create_menu_item_with_mnemonic(menu3, "_Group Structural", "CreateFuncGroup"); + create_menu_item_with_mnemonic(menu3, "_Combine Entities", "GroupSelection"); + create_menu_item_with_mnemonic(menu3, "_Connect", "ConnectSelection"); + create_menu_item_with_mnemonic(menu3, "_KillConnect", "KillConnectSelection"); + create_menu_item_with_mnemonic(menu3, "_Select Color...", "EntityColor"); + create_menu_item_with_mnemonic(menu3, "_Normalize Color...", "NormalizeColor"); + } + + if (GlobalSelectionSystem().countSelected() == 0) { + gtk_menu_popup(m_mnuDrop, 0, 0, 0, 0, 1, GDK_CURRENT_TIME); + } else if (GlobalSelectionSystem().countSelected() == 1) { + gtk_menu_popup(m_mnuDropSingle, 0, 0, 0, 0, 1, GDK_CURRENT_TIME); + } else { + gtk_menu_popup(m_mnuDropMultiple, 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(ui::Widget 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 ? m_parent : MainFrame_getWindow(), XYWnd_moveDelta, this); + m_move_focusOut = m_gl_widget.connect("focus_out_event", G_CALLBACK(XYWnd_Move_focusOut), this); +} + +void XYWnd::Move_End() +{ + m_move_started = false; + g_xywnd_freezePointer.unfreeze_pointer(m_parent ? 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(ui::Widget 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 ? m_parent : MainFrame_getWindow(), XYWnd_zoomDelta, this); + m_zoom_focusOut = m_gl_widget.connect("focus_out_event", G_CALLBACK(XYWnd_Zoom_focusOut), this); +} + +void XYWnd::Zoom_End() +{ + m_zoom_started = false; + g_xywnd_freezePointer.unfreeze_pointer(m_parent ? 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) { + 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 CamWnd_DisableMovement(); + +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)); + } + CamWnd_DisableMovement(); +} + +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], GetSnapGridSize()); + point[1] = float_snapped(point[1], GetSnapGridSize()); + } else if (m_viewType == YZ) { + point[1] = float_snapped(point[1], GetSnapGridSize()); + point[2] = float_snapped(point[2], GetSnapGridSize()); + } else { + point[0] = float_snapped(point[0], GetSnapGridSize()); + point[2] = float_snapped(point[2], GetSnapGridSize()); + } +} + +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) { + ui::alert(ui::root, "You have to select some brushes to get the bounding box for.\n", + "No selection", ui::alert_type::OK, ui::alert_icon::Error); + return; + } + + const char *filename = MainFrame_getWindow().file_dialog(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, a; + 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; + } + + a = ((GetSnapGridSize() > 0.0f) ? 1.0f : 0.3f); + + 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 || a < 1.0f) { + if (a < 1.0f) { + glEnable(GL_BLEND); + } + + if (COLORS_DIFFER(g_xywindow_globals.color_gridminor, g_xywindow_globals.color_gridback)) { + glColor4fv(vector4_to_array(Vector4(g_xywindow_globals.color_gridminor, a))); + + 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_gridminor)) { + glColor4fv(vector4_to_array(Vector4(g_xywindow_globals.color_gridmajor, a))); + + 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(); + } + + if (a < 1.0f) { + glDisable(GL_BLEND); + } + } + + // draw coordinate text if needed + if (g_xywindow_globals_private.show_coordinates) { + glColor4fv(vector4_to_array(Vector4(g_xywindow_globals.color_gridtext, 1.0f))); + float offx = m_vOrigin[nDim2] + h - (4 + GlobalOpenGL().m_font->getPixelAscent()) / m_fScale; + float offy = m_vOrigin[nDim1] - w + 4 / 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) { + glColor4f(1.0f, 0.0f, 0.0f, 1.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 < xe; x += g_xywindow_globals_private.blockSize) { + for (y = yb; y < ye; y += g_xywindow_globals_private.blockSize) { + glRasterPos2f(x + (g_xywindow_globals_private.blockSize / 2), + y + (g_xywindow_globals_private.blockSize / 2)); + sprintf(text, "%i,%i", (int) floor(x / g_xywindow_globals_private.blockSize), + (int) floor(y / g_xywindow_globals_private.blockSize)); + GlobalOpenGL().drawString(text); + } + } + } + + glColor4f(0, 0, 0, 0); +} + +void XYWnd::DrawCameraIcon(const Vector3 &origin, const Vector3 &angles) +{ + float x, y, fov, box; + double a; + + fov = 48 / m_fScale; + box = 16 / m_fScale; + + if (m_viewType == XY) { + x = origin[0]; + y = origin[1]; + a = degrees_to_radians(angles[CAMERA_YAW]); + } else if (m_viewType == YZ) { + x = origin[1]; + y = origin[2]; + a = degrees_to_radians(angles[CAMERA_PITCH]); + } else { + x = origin[0]; + y = origin[2]; + a = degrees_to_radians(angles[CAMERA_PITCH]); + } + + glColor3f(0.0, 0.0, 1.0); + glBegin(GL_LINE_STRIP); + glVertex3f(x - box, y, 0); + glVertex3f(x, y + (box / 2), 0); + glVertex3f(x + box, y, 0); + glVertex3f(x, y - (box / 2), 0); + glVertex3f(x - box, y, 0); + glVertex3f(x + box, y, 0); + glEnd(); + + glBegin(GL_LINE_STRIP); + glVertex3f(x + static_cast( 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; + } + } + + 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(); + + 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); + glVertex2f(0.5, 0.5); + glVertex2f(m_nWidth - 0.5, 1); + glVertex2f(m_nWidth - 0.5, m_nHeight - 0.5); + glVertex2f(0.5, m_nHeight - 0.5); + 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); + if (g_pParentWnd->GetXYWnd()) { + g_pParentWnd->GetXYWnd()->PositionView(position); + } + if (g_pParentWnd->GetXZWnd()) { + g_pParentWnd->GetXZWnd()->PositionView(position); + } + if (g_pParentWnd->GetYZWnd()) { + g_pParentWnd->GetYZWnd()->PositionView(position); + } +} + +void XY_Focus() +{ +#if 1 + // cannot do this in a split window + // do something else that the user may want here + XY_Split_Focus(); + return; +#else + XYWnd *xywnd = g_pParentWnd->GetXYWnd(); + XYWnd_Focus(xywnd); +#endif +} + +void XY_Top() +{ +#if 1 + // cannot do this in a split window + // do something else that the user may want here + XY_Split_Focus(); + return; +#else + XYWnd *xywnd = g_pParentWnd->GetXYWnd(); + xywnd->SetViewType(XY); + XYWnd_Focus(xywnd); +#endif +} + +void XY_Side() +{ +#if 1 + // cannot do this in a split window + // do something else that the user may want here + XY_Split_Focus(); + return; +#else + XYWnd *xywnd = g_pParentWnd->GetXYWnd(); + xywnd->SetViewType(XZ); + XYWnd_Focus(xywnd); +#endif +} + +void XY_Front() +{ +#if 1 + // cannot do this in a split window + // do something else that the user may want here + XY_Split_Focus(); + return; +#else + XYWnd *xywnd = g_pParentWnd->GetXYWnd(); + xywnd->SetViewType(YZ); + XYWnd_Focus(xywnd); +#endif +} + +void XY_Next() +{ +#if 1 + // cannot do this in a split window + // do something else that the user may want here + XY_Split_Focus(); + return; +#else + 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); +#endif +} + +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(ui::Window parent) +{ + g_xy_top_shown.connect(parent); +} + +ToggleShown g_yz_side_shown(false); + +void YZ_Side_Shown_Construct(ui::Window parent) +{ + g_yz_side_shown.connect(parent); +} + +ToggleShown g_xz_front_shown(false); + +void XZ_Front_Shown_Construct(ui::Window parent) +{ + g_xz_front_shown.connect(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) { + XYWnd::m_mnuDrop.destroy(); + XYWnd::m_mnuDrop = ui::Menu(ui::null); + } + if (XYWnd::m_mnuDropSingle) { + XYWnd::m_mnuDropSingle.destroy(); + XYWnd::m_mnuDropSingle = ui::Menu(ui::null); + } + if (XYWnd::m_mnuDropMultiple) { + XYWnd::m_mnuDropMultiple.destroy(); + XYWnd::m_mnuDropMultiple = ui::Menu(ui::null); + } + } + } +}; + +EntityClassMenu g_EntityClassMenu; + + +void ShowNamesToggle() +{ + GlobalEntityCreator().setShowNames(!GlobalEntityCreator().getShowNames()); + XY_UpdateAllWindows(); +} + +typedef FreeCaller ShowNamesToggleCaller; + +void ShowNamesExport(const Callback &importer) +{ + importer(GlobalEntityCreator().getShowNames()); +} + +typedef FreeCaller &), ShowNamesExport> ShowNamesExportCaller; + +void ShowAnglesToggle() +{ + GlobalEntityCreator().setShowAngles(!GlobalEntityCreator().getShowAngles()); + XY_UpdateAllWindows(); +} + +typedef FreeCaller ShowAnglesToggleCaller; + +void ShowAnglesExport(const Callback &importer) +{ + importer(GlobalEntityCreator().getShowAngles()); +} + +typedef FreeCaller &), ShowAnglesExport> ShowAnglesExportCaller; + +void ShowBlocksToggle() +{ + g_xywindow_globals_private.show_blocks ^= 1; + XY_UpdateAllWindows(); +} + +typedef FreeCaller ShowBlocksToggleCaller; + +void ShowBlocksExport(const Callback &importer) +{ + importer(g_xywindow_globals_private.show_blocks); +} + +typedef FreeCaller &), ShowBlocksExport> ShowBlocksExportCaller; + +void ShowCoordinatesToggle() +{ + g_xywindow_globals_private.show_coordinates ^= 1; + XY_UpdateAllWindows(); +} + +typedef FreeCaller ShowCoordinatesToggleCaller; + +void ShowCoordinatesExport(const Callback &importer) +{ + importer(g_xywindow_globals_private.show_coordinates); +} + +typedef FreeCaller &), ShowCoordinatesExport> ShowCoordinatesExportCaller; + +void ShowOutlineToggle() +{ + g_xywindow_globals_private.show_outline ^= 1; + XY_UpdateAllWindows(); +} + +typedef FreeCaller ShowOutlineToggleCaller; + +void ShowOutlineExport(const Callback &importer) +{ + importer(g_xywindow_globals_private.show_outline); +} + +typedef FreeCaller &), ShowOutlineExport> ShowOutlineExportCaller; + +void ShowAxesToggle() +{ + g_xywindow_globals_private.show_axis ^= 1; + XY_UpdateAllWindows(); +} + +typedef FreeCaller ShowAxesToggleCaller; + +void ShowAxesExport(const Callback &importer) +{ + importer(g_xywindow_globals_private.show_axis); +} + +typedef FreeCaller &), ShowAxesExport> ShowAxesExportCaller; + +void ShowWorkzoneToggle() +{ + g_xywindow_globals_private.d_show_work ^= 1; + XY_UpdateAllWindows(); +} + +typedef FreeCaller ShowWorkzoneToggleCaller; + +void ShowWorkzoneExport(const Callback &importer) +{ + importer(g_xywindow_globals_private.d_show_work); +} + +typedef FreeCaller &), ShowWorkzoneExport> ShowWorkzoneExportCaller; + +ShowNamesExportCaller g_show_names_caller; +Callback &)> g_show_names_callback(g_show_names_caller); +ToggleItem g_show_names(g_show_names_callback); + +ShowAnglesExportCaller g_show_angles_caller; +Callback &)> g_show_angles_callback(g_show_angles_caller); +ToggleItem g_show_angles(g_show_angles_callback); + +ShowBlocksExportCaller g_show_blocks_caller; +Callback &)> g_show_blocks_callback(g_show_blocks_caller); +ToggleItem g_show_blocks(g_show_blocks_callback); + +ShowCoordinatesExportCaller g_show_coordinates_caller; +Callback &)> g_show_coordinates_callback(g_show_coordinates_caller); +ToggleItem g_show_coordinates(g_show_coordinates_callback); + +ShowOutlineExportCaller g_show_outline_caller; +Callback &)> g_show_outline_callback(g_show_outline_caller); +ToggleItem g_show_outline(g_show_outline_callback); + +ShowAxesExportCaller g_show_axes_caller; +Callback &)> g_show_axes_callback(g_show_axes_caller); +ToggleItem g_show_axes(g_show_axes_callback); + +ShowWorkzoneExportCaller g_show_workzone_caller; +Callback &)> 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(makeCallbackF(Orthographic_constructPage)); +} + +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(makeCallbackF(Clipper_constructPage)); +} + + +#include "preferencesystem.h" +#include "stringio.h" + + +struct ToggleShown_Bool { + static void Export(const ToggleShown &self, const Callback &returnz) + { + returnz(self.active()); + } + + static void Import(ToggleShown &self, bool value) + { + self.set(value); + } +}; + + +void XYWindow_Construct() +{ + GlobalCommands_insert("ToggleCrosshairs", makeCallbackF(ToggleShowCrosshair), + Accelerator('X', (GdkModifierType) GDK_SHIFT_MASK)); + GlobalCommands_insert("ToggleSizePaint", makeCallbackF(ToggleShowSizeInfo), Accelerator('J')); + GlobalCommands_insert("ToggleGrid", makeCallbackF(ToggleShowGrid), 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", makeCallbackF(XY_Next), Accelerator(GDK_KEY_Tab, + (GdkModifierType) GDK_CONTROL_MASK)); // fixme: doesn't show its shortcut + GlobalCommands_insert("ZoomIn", makeCallbackF(XY_ZoomIn), Accelerator(GDK_KEY_Delete)); + GlobalCommands_insert("ZoomOut", makeCallbackF(XY_ZoomOut), Accelerator(GDK_KEY_Insert)); + GlobalCommands_insert("ViewTop", makeCallbackF(XY_Top), Accelerator(GDK_KEY_KP_Home)); + GlobalCommands_insert("ViewSide", makeCallbackF(XY_Side), Accelerator(GDK_KEY_KP_Page_Down)); + GlobalCommands_insert("ViewFront", makeCallbackF(XY_Front), Accelerator(GDK_KEY_KP_End)); + GlobalCommands_insert("Zoom100", makeCallbackF(XY_Zoom100)); + GlobalCommands_insert("CenterXYView", makeCallbackF(XY_Focus), + Accelerator(GDK_KEY_Tab, (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK))); + + GlobalPreferenceSystem().registerPreference("ClipCaulk", make_property_string(g_clip_useCaulk)); + + GlobalPreferenceSystem().registerPreference("NewRightClick", + make_property_string(g_xywindow_globals.m_bRightClick)); + GlobalPreferenceSystem().registerPreference("ChaseMouse", + make_property_string(g_xywindow_globals_private.m_bChaseMouse)); + GlobalPreferenceSystem().registerPreference("SizePainting", + make_property_string(g_xywindow_globals_private.m_bSizePaint)); + GlobalPreferenceSystem().registerPreference("NoStipple", make_property_string(g_xywindow_globals.m_bNoStipple)); + GlobalPreferenceSystem().registerPreference("SI_ShowCoords", + make_property_string(g_xywindow_globals_private.show_coordinates)); + GlobalPreferenceSystem().registerPreference("SI_ShowOutlines", + make_property_string(g_xywindow_globals_private.show_outline)); + GlobalPreferenceSystem().registerPreference("SI_ShowAxis", + make_property_string(g_xywindow_globals_private.show_axis)); + GlobalPreferenceSystem().registerPreference("CamXYUpdate", + make_property_string(g_xywindow_globals_private.m_bCamXYUpdate)); + GlobalPreferenceSystem().registerPreference("ShowWorkzone", + make_property_string(g_xywindow_globals_private.d_show_work)); + + GlobalPreferenceSystem().registerPreference("SI_AxisColors0", make_property_string(g_xywindow_globals.AxisColorX)); + GlobalPreferenceSystem().registerPreference("SI_AxisColors1", make_property_string(g_xywindow_globals.AxisColorY)); + GlobalPreferenceSystem().registerPreference("SI_AxisColors2", make_property_string(g_xywindow_globals.AxisColorZ)); + GlobalPreferenceSystem().registerPreference("SI_Colors1", make_property_string(g_xywindow_globals.color_gridback)); + GlobalPreferenceSystem().registerPreference("SI_Colors2", make_property_string(g_xywindow_globals.color_gridminor)); + GlobalPreferenceSystem().registerPreference("SI_Colors3", make_property_string(g_xywindow_globals.color_gridmajor)); + GlobalPreferenceSystem().registerPreference("SI_Colors6", make_property_string(g_xywindow_globals.color_gridblock)); + GlobalPreferenceSystem().registerPreference("SI_Colors7", make_property_string(g_xywindow_globals.color_gridtext)); + GlobalPreferenceSystem().registerPreference("SI_Colors8", make_property_string(g_xywindow_globals.color_brushes)); + GlobalPreferenceSystem().registerPreference("SI_Colors14", + make_property_string(g_xywindow_globals.color_gridmajor_alt)); + + + GlobalPreferenceSystem().registerPreference("XZVIS", make_property_string(g_xz_front_shown)); + GlobalPreferenceSystem().registerPreference("YZVIS", make_property_string(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 0000000..4edfc63 --- /dev/null +++ b/radiant/xywindow.h @@ -0,0 +1,366 @@ +/* + Copyright (C) 1999-2006 Id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along 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; +} + + +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 { + ui::GLArea m_gl_widget; + guint m_sizeHandler; + guint m_exposeHandler; + + DeferredDraw m_deferredDraw; + DeferredMotion m_deferred_motion; +public: + ui::Window m_parent; + + XYWnd(); + + ~XYWnd(); + + void queueDraw() + { + m_deferredDraw.draw(); + } + + ui::GLArea 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 ui::Menu m_mnuDrop; + static ui::Menu m_mnuDropSingle; + static ui::Menu m_mnuDropMultiple; + + 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 Member 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(0.0f, 0.0f, 0.0f), + color_gridmajor(0.45f, 0.45f, 0.45f), + color_gridminor(0.2f, 0.2f, 0.2f), + color_gridblock(0.39f, 0.18f, 0.0f), + color_gridtext(0.0f, 0.0f, 0.0f), + color_brushes(0.55f, 0.55f, 0.55f), + color_selbrushes(1.0f, 0.0f, 0.0f), + color_clipper(0.0f, 0.0f, 1.0f), + color_viewname(0.7f, 0.7f, 0.0f), + 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(); + +void XY_Top_Shown_Construct(ui::Window parent); + +void YZ_Side_Shown_Construct(ui::Window parent); + +void XZ_Front_Shown_Construct(ui::Window parent); + +void XYWindow_Construct(); + +void XYWindow_Destroy(); + +void WXY_Print(); + +void WXY_BackgroundSelect(); + +void XYShow_registerCommands(); + +void XYWnd_registerShortcuts(); + +#endif diff --git a/regression_tests/q3map2/base_winding/README.txt b/regression_tests/q3map2/base_winding/README.txt new file mode 100644 index 0000000..599ec3d --- /dev/null +++ b/regression_tests/q3map2/base_winding/README.txt @@ -0,0 +1,8 @@ +DESCRIPTION OF PROBLEM: +======================= + +The provided map, maps/base_winding.map, serves as an example map to test +changes to BaseWindingForPlane(). This map has planes at many different +angles. Use the patch base_winding_logging.patch to log the computed +base windings, in order to compare before and after when making changes to +BaseWindingForPlane(). diff --git a/regression_tests/q3map2/base_winding/base_winding_logging.patch b/regression_tests/q3map2/base_winding/base_winding_logging.patch new file mode 100644 index 0000000..20fbaee --- /dev/null +++ b/regression_tests/q3map2/base_winding/base_winding_logging.patch @@ -0,0 +1,27 @@ +Index: tools/quake3/q3map2/brush.c +=================================================================== +--- tools/quake3/q3map2/brush.c (revision 369) ++++ tools/quake3/q3map2/brush.c (working copy) +@@ -357,6 +357,8 @@ + side_t *side; + plane_t *plane; + ++ static int brushord = -1; ++ brushord++; + + /* walk the list of brush sides */ + for( i = 0; i < brush->numsides; i++ ) +@@ -367,6 +369,13 @@ + + /* make huge winding */ + w = BaseWindingForPlane( plane->normal, plane->dist ); ++ Sys_Printf(">>> BaseWindingForPlane() for brush %i, side %i is as follows:\n", brushord, i); ++ for (j = 0; j < w->numpoints; j++) { ++ Sys_Printf(">>> (%.1f %.1f %.1f) [rounded to nearest integer coordinates]\n", ++ Q_rint(w->p[j][0]), ++ Q_rint(w->p[j][1]), ++ Q_rint(w->p[j][2])); ++ } + + /* walk the list of brush sides */ + for( j = 0; j < brush->numsides && w != NULL; j++ ) diff --git a/regression_tests/q3map2/base_winding/maps/base_winding.map b/regression_tests/q3map2/base_winding/maps/base_winding.map new file mode 100644 index 0000000..237278a --- /dev/null +++ b/regression_tests/q3map2/base_winding/maps/base_winding.map @@ -0,0 +1,1284 @@ +{ +"classname" "worldspawn" +{ +( 768 256 0 ) ( 768 115 -256 ) ( 768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 768 256 0 ) ( 512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 768 115 -256 ) ( 443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 512 0 0 ) ( 768 0 0 ) ( 428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 512 0 0 ) ( 428 115 -256 ) ( 443 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 128 -443 ) ( 768 0 -443 ) ( 768 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 768 128 -443 ) ( 428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 768 0 -443 ) ( 222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 768 115 -256 ) ( 256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 256 0 -443 ) ( 222 128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 128 -768 ) ( 768 0 -768 ) ( 768 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 768 128 -443 ) ( 256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -768 ) ( 768 128 -768 ) ( 222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -768 ) ( 768 0 -768 ) ( 222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 768 0 -443 ) ( 256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 256 0 -768 ) ( 222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 222 128 -768 ) ( 0 0 -768 ) ( 256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 222 128 -768 ) ( 256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( 222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 256 0 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 0 0 -512 ) ( 222 128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 314 -256 ) ( 768 115 -256 ) ( 768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 768 314 -256 ) ( 443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 768 115 -256 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 768 256 0 ) ( 428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 428 115 -256 ) ( 314 314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 314 -256 ) ( 768 128 -443 ) ( 768 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 768 314 -256 ) ( 428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 768 128 -443 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 768 115 -256 ) ( 222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 222 128 -443 ) ( 314 314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 443 0 ) ( 768 314 -256 ) ( 768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 768 443 0 ) ( 443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 768 314 -256 ) ( 256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 768 256 0 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 314 314 -256 ) ( 256 443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 222 -443 ) ( 768 128 -443 ) ( 768 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 768 222 -443 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 768 128 -443 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 768 314 -256 ) ( 222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 222 128 -443 ) ( 128 222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 222 -768 ) ( 768 128 -768 ) ( 768 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 768 222 -443 ) ( 222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -768 ) ( 768 222 -768 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -768 ) ( 768 128 -768 ) ( 128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 768 128 -443 ) ( 222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 222 128 -768 ) ( 128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 128 222 -768 ) ( 0 0 -768 ) ( 222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 222 -768 ) ( 222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 222 128 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 -443 ) ( 0 0 -512 ) ( 128 222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 115 768 -256 ) ( 314 768 -256 ) ( 256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 115 768 -256 ) ( 256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 314 768 -256 ) ( 115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 256 768 0 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 314 314 -256 ) ( 115 428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 768 -256 ) ( 768 768 0 ) ( 256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 314 768 -256 ) ( 256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 314 -256 ) ( 768 768 -256 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 443 0 ) ( 768 768 0 ) ( 768 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 256 768 0 ) ( 768 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 768 443 0 ) ( 768 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 115 768 -256 ) ( 128 768 -443 ) ( 314 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 115 768 -256 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 768 -443 ) ( 115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 314 768 -256 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 128 222 -443 ) ( 115 428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 768 -443 ) ( 768 768 -256 ) ( 314 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 768 -443 ) ( 314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 222 -443 ) ( 768 768 -443 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 314 -256 ) ( 768 768 -256 ) ( 768 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 314 768 -256 ) ( 768 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 -256 ) ( 768 314 -256 ) ( 768 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 768 768 -768 ) ( 128 768 -768 ) ( 128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 222 -443 ) ( 768 222 -768 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 768 -443 ) ( 768 768 -768 ) ( 768 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 768 -443 ) ( 128 768 -768 ) ( 768 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 222 -768 ) ( 128 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 768 -443 ) ( 768 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 768 0 ) ( 115 768 -256 ) ( 256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 512 0 ) ( 0 768 0 ) ( 256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 115 768 -256 ) ( 0 512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 256 768 0 ) ( 115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 115 428 -256 ) ( 0 512 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 0 768 -443 ) ( 128 768 -443 ) ( 115 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -443 ) ( 0 768 -443 ) ( 115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 768 -443 ) ( 0 256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 115 768 -256 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 128 222 -443 ) ( 0 256 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 0 768 -768 ) ( 128 768 -768 ) ( 128 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -443 ) ( 0 768 -443 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -768 ) ( 0 768 -768 ) ( 0 256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -768 ) ( 128 768 -768 ) ( 0 256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 768 -443 ) ( 128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 222 -768 ) ( 0 256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 256 -768 ) ( 0 0 -768 ) ( 128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -443 ) ( 0 256 -768 ) ( 128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( 0 256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 128 222 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 -443 ) ( 0 0 -512 ) ( 0 256 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -115 768 -256 ) ( 115 768 -256 ) ( 0 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 -256 ) ( -115 768 -256 ) ( 0 512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 115 768 -256 ) ( -115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 512 0 ) ( 0 768 0 ) ( 115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 512 0 ) ( 115 428 -256 ) ( -115 428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -115 768 -256 ) ( 0 768 -443 ) ( 115 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 -256 ) ( -115 768 -256 ) ( 115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -443 ) ( 0 768 -443 ) ( -115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 115 768 -256 ) ( 0 256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 -256 ) ( 0 256 -443 ) ( -115 428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 0 -512 0 ) ( 115 -428 -256 ) ( 256 -443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 0 -512 0 ) ( 0 -768 0 ) ( 115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 -256 ) ( 115 -768 -256 ) ( 256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 -443 0 ) ( 256 -768 0 ) ( 0 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 -768 0 ) ( 115 -768 -256 ) ( 0 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 115 -428 -256 ) ( 0 -256 -443 ) ( 128 -222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 115 -428 -256 ) ( 115 -768 -256 ) ( 0 -256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -443 ) ( 0 -768 -443 ) ( 128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -443 ) ( 128 -768 -443 ) ( 115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -768 -443 ) ( 0 -768 -443 ) ( 115 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 -256 -443 ) ( 0 -256 -768 ) ( 128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -443 ) ( 0 -768 -443 ) ( 0 -256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -768 ) ( 0 -768 -768 ) ( 128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -768 ) ( 128 -768 -768 ) ( 128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -443 ) ( 128 -768 -443 ) ( 0 -256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -768 -768 ) ( 0 -768 -768 ) ( 0 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 -256 -443 ) ( 0 0 -512 ) ( 128 -222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 0 -256 -443 ) ( 0 -256 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( 128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -443 ) ( 128 -222 -768 ) ( 0 -256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -768 ) ( 0 0 -768 ) ( 0 -256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 -443 0 ) ( 115 -428 -256 ) ( 314 -314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 256 -443 0 ) ( 256 -768 0 ) ( 115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 -256 ) ( 115 -768 -256 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 -256 ) ( 314 -768 -256 ) ( 256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -768 -256 ) ( 115 -768 -256 ) ( 256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 115 -428 -256 ) ( 128 -222 -443 ) ( 314 -314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 115 -428 -256 ) ( 115 -768 -256 ) ( 128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -443 ) ( 128 -768 -443 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 -256 ) ( 314 -768 -256 ) ( 115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -768 -256 ) ( 128 -768 -443 ) ( 115 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 -443 0 ) ( 314 -314 -256 ) ( 443 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 256 -443 0 ) ( 256 -768 0 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 -256 ) ( 314 -768 -256 ) ( 443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 -256 0 ) ( 443 -768 0 ) ( 256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 -768 0 ) ( 314 -768 -256 ) ( 256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 314 -314 -256 ) ( 128 -222 -443 ) ( 222 -128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 314 -314 -256 ) ( 314 -768 -256 ) ( 128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -443 ) ( 128 -768 -443 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 222 -768 -443 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -768 -443 ) ( 128 -768 -443 ) ( 314 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 128 -222 -443 ) ( 128 -222 -768 ) ( 222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -443 ) ( 128 -768 -443 ) ( 128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 -768 ) ( 128 -768 -768 ) ( 222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -768 ) ( 222 -768 -768 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 222 -768 -443 ) ( 128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -768 -768 ) ( 128 -768 -768 ) ( 128 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 128 -222 -443 ) ( 0 0 -512 ) ( 222 -128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 128 -222 -443 ) ( 128 -222 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 222 -128 -768 ) ( 128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -768 ) ( 0 0 -768 ) ( 128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 443 -256 0 ) ( 314 -314 -256 ) ( 428 -115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 443 -256 0 ) ( 768 -256 0 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 -256 ) ( 768 -314 -256 ) ( 428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 -256 ) ( 768 -115 -256 ) ( 443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -115 -256 ) ( 768 -314 -256 ) ( 768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 443 -256 0 ) ( 443 -768 0 ) ( 314 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 -256 0 ) ( 768 -256 0 ) ( 443 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 -768 0 ) ( 768 -768 0 ) ( 314 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -768 -256 ) ( 768 -768 -256 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 -256 ) ( 768 -314 -256 ) ( 443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 -256 ) ( 768 -768 0 ) ( 768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 314 -314 -256 ) ( 222 -128 -443 ) ( 428 -115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 314 -314 -256 ) ( 768 -314 -256 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 768 -128 -443 ) ( 428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 -256 ) ( 768 -115 -256 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -115 -256 ) ( 768 -128 -443 ) ( 768 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 314 -314 -256 ) ( 314 -768 -256 ) ( 222 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 -256 ) ( 768 -314 -256 ) ( 314 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -768 -256 ) ( 768 -768 -256 ) ( 222 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -768 -443 ) ( 768 -768 -443 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 768 -128 -443 ) ( 314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 -443 ) ( 768 -768 -256 ) ( 768 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 222 -128 -443 ) ( 768 -128 -443 ) ( 768 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 222 -128 -768 ) ( 768 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -128 -443 ) ( 768 -128 -768 ) ( 768 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 -443 ) ( 768 -768 -768 ) ( 222 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -768 -443 ) ( 222 -768 -768 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 -768 ) ( 768 -128 -768 ) ( 222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 443 -256 0 ) ( 428 -115 -256 ) ( 512 0 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 443 -256 0 ) ( 768 -256 0 ) ( 428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 -256 ) ( 768 -115 -256 ) ( 512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 512 0 0 ) ( 768 0 0 ) ( 443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 0 0 ) ( 768 -115 -256 ) ( 768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 428 -115 -256 ) ( 222 -128 -443 ) ( 256 0 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 428 -115 -256 ) ( 768 -115 -256 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 768 -128 -443 ) ( 256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 768 0 -443 ) ( 428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 0 -443 ) ( 768 -128 -443 ) ( 768 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 222 -128 -443 ) ( 222 -128 -768 ) ( 256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -443 ) ( 768 -128 -443 ) ( 222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 -768 ) ( 768 -128 -768 ) ( 256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -768 ) ( 768 0 -768 ) ( 256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 768 0 -443 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 0 -768 ) ( 768 -128 -768 ) ( 768 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 222 -128 -443 ) ( 0 0 -512 ) ( 256 0 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 222 -128 -443 ) ( 222 -128 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( 256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 256 0 -768 ) ( 222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -768 ) ( 0 0 -768 ) ( 222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 512 0 0 ) ( 428 -115 -256 ) ( 428 115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 512 0 0 ) ( 768 0 0 ) ( 428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 -256 ) ( 768 -115 -256 ) ( 428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 768 115 -256 ) ( 512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 115 -256 ) ( 768 -115 -256 ) ( 768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 428 -115 -256 ) ( 256 0 -443 ) ( 428 115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 428 -115 -256 ) ( 768 -115 -256 ) ( 256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 -443 ) ( 768 0 -443 ) ( 428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 -256 ) ( 768 115 -256 ) ( 428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 115 -256 ) ( 768 0 -443 ) ( 768 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -768 -256 0 ) ( -768 -115 -256 ) ( -768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -768 -256 0 ) ( -512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -768 -115 -256 ) ( -443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -512 0 0 ) ( -768 0 0 ) ( -428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -512 0 0 ) ( -428 -115 -256 ) ( -443 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -128 -443 ) ( -768 0 -443 ) ( -768 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -768 -128 -443 ) ( -428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -768 0 -443 ) ( -222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -768 -115 -256 ) ( -256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -256 0 -443 ) ( -222 -128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -128 -768 ) ( -768 0 -768 ) ( -768 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -768 -128 -443 ) ( -256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -768 ) ( -768 -128 -768 ) ( -222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -768 ) ( -768 0 -768 ) ( -222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -768 0 -443 ) ( -256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -256 0 -768 ) ( -222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -222 -128 -768 ) ( 0 0 -768 ) ( -256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -222 -128 -768 ) ( -256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( -222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -256 0 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( 0 0 -512 ) ( -222 -128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -314 -256 ) ( -768 -115 -256 ) ( -768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -768 -314 -256 ) ( -443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -768 -115 -256 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -768 -256 0 ) ( -428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -428 -115 -256 ) ( -314 -314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -314 -256 ) ( -768 -128 -443 ) ( -768 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -768 -314 -256 ) ( -428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -768 -128 -443 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -768 -115 -256 ) ( -222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -222 -128 -443 ) ( -314 -314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -443 0 ) ( -768 -314 -256 ) ( -768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -768 -443 0 ) ( -443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -768 -314 -256 ) ( -256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -768 -256 0 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -314 -314 -256 ) ( -256 -443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -222 -443 ) ( -768 -128 -443 ) ( -768 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -768 -222 -443 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -768 -128 -443 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -768 -314 -256 ) ( -222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -222 -128 -443 ) ( -128 -222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -222 -768 ) ( -768 -128 -768 ) ( -768 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -768 -222 -443 ) ( -222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -768 ) ( -768 -222 -768 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -768 ) ( -768 -128 -768 ) ( -128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -768 -128 -443 ) ( -222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -222 -128 -768 ) ( -128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -128 -222 -768 ) ( 0 0 -768 ) ( -222 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -222 -768 ) ( -222 -128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( -222 -128 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 -443 ) ( 0 0 -512 ) ( -128 -222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -115 -768 -256 ) ( -314 -768 -256 ) ( -256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( -115 -768 -256 ) ( -256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -314 -768 -256 ) ( -115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -256 -768 0 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -314 -314 -256 ) ( -115 -428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -768 -256 ) ( -768 -768 0 ) ( -256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -314 -768 -256 ) ( -256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -314 -256 ) ( -768 -768 -256 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -443 0 ) ( -768 -768 0 ) ( -768 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -256 -768 0 ) ( -768 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -768 -443 0 ) ( -768 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -115 -768 -256 ) ( -128 -768 -443 ) ( -314 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( -115 -768 -256 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -768 -443 ) ( -115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -314 -768 -256 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -128 -222 -443 ) ( -115 -428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -768 -443 ) ( -768 -768 -256 ) ( -314 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -768 -443 ) ( -314 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -222 -443 ) ( -768 -768 -443 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -314 -256 ) ( -768 -768 -256 ) ( -768 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -314 -768 -256 ) ( -768 -314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 -256 ) ( -768 -314 -256 ) ( -768 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -768 -768 -768 ) ( -128 -768 -768 ) ( -128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -222 -443 ) ( -768 -222 -768 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -768 -443 ) ( -768 -768 -768 ) ( -768 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -768 -443 ) ( -128 -768 -768 ) ( -768 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -222 -768 ) ( -128 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -768 -443 ) ( -768 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 -768 0 ) ( -115 -768 -256 ) ( -256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -512 0 ) ( 0 -768 0 ) ( -256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( -115 -768 -256 ) ( 0 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -256 -768 0 ) ( -115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -115 -428 -256 ) ( 0 -512 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 0 -768 -443 ) ( -128 -768 -443 ) ( -115 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -443 ) ( 0 -768 -443 ) ( -115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -768 -443 ) ( 0 -256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( -115 -768 -256 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( -128 -222 -443 ) ( 0 -256 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 0 -768 -768 ) ( -128 -768 -768 ) ( -128 -768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -443 ) ( 0 -768 -443 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -768 ) ( 0 -768 -768 ) ( 0 -256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -768 ) ( -128 -768 -768 ) ( 0 -256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -768 -443 ) ( -128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -222 -768 ) ( 0 -256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 -256 -768 ) ( 0 0 -768 ) ( -128 -222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -443 ) ( 0 -256 -768 ) ( -128 -222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( 0 -256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( -128 -222 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 -443 ) ( 0 0 -512 ) ( 0 -256 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 115 -768 -256 ) ( -115 -768 -256 ) ( 0 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 -256 ) ( 115 -768 -256 ) ( 0 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( -115 -768 -256 ) ( 115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -512 0 ) ( 0 -768 0 ) ( -115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -512 0 ) ( -115 -428 -256 ) ( 115 -428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 115 -768 -256 ) ( 0 -768 -443 ) ( -115 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 -256 ) ( 115 -768 -256 ) ( -115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 -443 ) ( 0 -768 -443 ) ( 115 -428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( -115 -768 -256 ) ( 0 -256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 -256 ) ( 0 -256 -443 ) ( 115 -428 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 0 512 0 ) ( -115 428 -256 ) ( -256 443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 0 512 0 ) ( 0 768 0 ) ( -115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 -256 ) ( -115 768 -256 ) ( -256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 443 0 ) ( -256 768 0 ) ( 0 512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 768 0 ) ( -115 768 -256 ) ( 0 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -115 428 -256 ) ( 0 256 -443 ) ( -128 222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -115 428 -256 ) ( -115 768 -256 ) ( 0 256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -443 ) ( 0 768 -443 ) ( -128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -443 ) ( -128 768 -443 ) ( -115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 768 -443 ) ( 0 768 -443 ) ( -115 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 256 -443 ) ( 0 256 -768 ) ( -128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -443 ) ( 0 768 -443 ) ( 0 256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 -768 ) ( 0 768 -768 ) ( -128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -768 ) ( -128 768 -768 ) ( -128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -443 ) ( -128 768 -443 ) ( 0 256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 768 -768 ) ( 0 768 -768 ) ( 0 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 256 -443 ) ( 0 0 -512 ) ( -128 222 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 0 256 -443 ) ( 0 256 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( -128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -443 ) ( -128 222 -768 ) ( 0 256 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -768 ) ( 0 0 -768 ) ( 0 256 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 443 0 ) ( -115 428 -256 ) ( -314 314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -256 443 0 ) ( -256 768 0 ) ( -115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 -256 ) ( -115 768 -256 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 -256 ) ( -314 768 -256 ) ( -256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 768 -256 ) ( -115 768 -256 ) ( -256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -115 428 -256 ) ( -128 222 -443 ) ( -314 314 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -115 428 -256 ) ( -115 768 -256 ) ( -128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -443 ) ( -128 768 -443 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 -256 ) ( -314 768 -256 ) ( -115 428 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 768 -256 ) ( -128 768 -443 ) ( -115 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 443 0 ) ( -314 314 -256 ) ( -443 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -256 443 0 ) ( -256 768 0 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 -256 ) ( -314 768 -256 ) ( -443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 256 0 ) ( -443 768 0 ) ( -256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 768 0 ) ( -314 768 -256 ) ( -256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -314 314 -256 ) ( -128 222 -443 ) ( -222 128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -314 314 -256 ) ( -314 768 -256 ) ( -128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -443 ) ( -128 768 -443 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -222 768 -443 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 768 -443 ) ( -128 768 -443 ) ( -314 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -128 222 -443 ) ( -128 222 -768 ) ( -222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -443 ) ( -128 768 -443 ) ( -128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 -768 ) ( -128 768 -768 ) ( -222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -768 ) ( -222 768 -768 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -222 768 -443 ) ( -128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 768 -768 ) ( -128 768 -768 ) ( -128 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -128 222 -443 ) ( 0 0 -512 ) ( -222 128 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -128 222 -443 ) ( -128 222 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -222 128 -768 ) ( -128 222 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -768 ) ( 0 0 -768 ) ( -128 222 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -443 256 0 ) ( -314 314 -256 ) ( -428 115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -443 256 0 ) ( -768 256 0 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 -256 ) ( -768 314 -256 ) ( -428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 -256 ) ( -768 115 -256 ) ( -443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 115 -256 ) ( -768 314 -256 ) ( -768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -443 256 0 ) ( -443 768 0 ) ( -314 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 256 0 ) ( -768 256 0 ) ( -443 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 768 0 ) ( -768 768 0 ) ( -314 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 768 -256 ) ( -768 768 -256 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 -256 ) ( -768 314 -256 ) ( -443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 -256 ) ( -768 768 0 ) ( -768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -314 314 -256 ) ( -222 128 -443 ) ( -428 115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -314 314 -256 ) ( -768 314 -256 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -768 128 -443 ) ( -428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 -256 ) ( -768 115 -256 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 115 -256 ) ( -768 128 -443 ) ( -768 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -314 314 -256 ) ( -314 768 -256 ) ( -222 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 -256 ) ( -768 314 -256 ) ( -314 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 768 -256 ) ( -768 768 -256 ) ( -222 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 768 -443 ) ( -768 768 -443 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -768 128 -443 ) ( -314 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 -443 ) ( -768 768 -256 ) ( -768 314 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -222 128 -443 ) ( -768 128 -443 ) ( -768 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -222 128 -768 ) ( -768 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 128 -443 ) ( -768 128 -768 ) ( -768 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 -443 ) ( -768 768 -768 ) ( -222 768 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 768 -443 ) ( -222 768 -768 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 -768 ) ( -768 128 -768 ) ( -222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -443 256 0 ) ( -428 115 -256 ) ( -512 0 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -443 256 0 ) ( -768 256 0 ) ( -428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 -256 ) ( -768 115 -256 ) ( -512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -512 0 0 ) ( -768 0 0 ) ( -443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 0 0 ) ( -768 115 -256 ) ( -768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -428 115 -256 ) ( -222 128 -443 ) ( -256 0 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -428 115 -256 ) ( -768 115 -256 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -768 128 -443 ) ( -256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -768 0 -443 ) ( -428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 0 -443 ) ( -768 128 -443 ) ( -768 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -222 128 -443 ) ( -222 128 -768 ) ( -256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -443 ) ( -768 128 -443 ) ( -222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 -768 ) ( -768 128 -768 ) ( -256 0 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -768 ) ( -768 0 -768 ) ( -256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -768 0 -443 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 0 -768 ) ( -768 128 -768 ) ( -768 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -222 128 -443 ) ( 0 0 -512 ) ( -256 0 -443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -222 128 -443 ) ( -222 128 -768 ) ( 0 0 -512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 -512 ) ( 0 0 -768 ) ( -256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -256 0 -768 ) ( -222 128 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -768 ) ( 0 0 -768 ) ( -222 128 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -512 0 0 ) ( -428 115 -256 ) ( -428 -115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -512 0 0 ) ( -768 0 0 ) ( -428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 -256 ) ( -768 115 -256 ) ( -428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -768 -115 -256 ) ( -512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -115 -256 ) ( -768 115 -256 ) ( -768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -428 115 -256 ) ( -256 0 -443 ) ( -428 -115 -256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -428 115 -256 ) ( -768 115 -256 ) ( -256 0 -443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 -443 ) ( -768 0 -443 ) ( -428 -115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 -256 ) ( -768 -115 -256 ) ( -428 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -115 -256 ) ( -768 0 -443 ) ( -768 115 -256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 512 0 0 ) ( 428 -115 256 ) ( 443 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 512 0 0 ) ( 768 0 0 ) ( 428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 256 ) ( 768 -115 256 ) ( 443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 -256 0 ) ( 768 -256 0 ) ( 512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -256 0 ) ( 768 -115 256 ) ( 768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 428 -115 256 ) ( 256 0 443 ) ( 222 -128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 428 -115 256 ) ( 768 -115 256 ) ( 256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 443 ) ( 768 0 443 ) ( 222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 443 ) ( 768 -128 443 ) ( 428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -128 443 ) ( 768 0 443 ) ( 768 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 0 443 ) ( 256 0 768 ) ( 222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 443 ) ( 768 0 443 ) ( 256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 768 ) ( 768 0 768 ) ( 222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 768 ) ( 768 -128 768 ) ( 222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 443 ) ( 768 -128 443 ) ( 256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -128 768 ) ( 768 0 768 ) ( 768 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 0 443 ) ( 0 0 512 ) ( 222 -128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 256 0 443 ) ( 256 0 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( 222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 443 ) ( 222 -128 768 ) ( 256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 768 ) ( 0 0 768 ) ( 256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 443 -256 0 ) ( 428 -115 256 ) ( 314 -314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 443 -256 0 ) ( 768 -256 0 ) ( 428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 256 ) ( 768 -115 256 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 256 ) ( 768 -314 256 ) ( 443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -314 256 ) ( 768 -115 256 ) ( 768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 428 -115 256 ) ( 222 -128 443 ) ( 314 -314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 428 -115 256 ) ( 768 -115 256 ) ( 222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 443 ) ( 768 -128 443 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 256 ) ( 768 -314 256 ) ( 428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -314 256 ) ( 768 -128 443 ) ( 768 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 443 -256 0 ) ( 314 -314 256 ) ( 256 -443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 443 -256 0 ) ( 768 -256 0 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 256 ) ( 768 -314 256 ) ( 256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 -443 0 ) ( 768 -443 0 ) ( 443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -443 0 ) ( 768 -314 256 ) ( 768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 314 -314 256 ) ( 222 -128 443 ) ( 128 -222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 314 -314 256 ) ( 768 -314 256 ) ( 222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 443 ) ( 768 -128 443 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 768 -222 443 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -222 443 ) ( 768 -128 443 ) ( 768 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 222 -128 443 ) ( 222 -128 768 ) ( 128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 443 ) ( 768 -128 443 ) ( 222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 -128 768 ) ( 768 -128 768 ) ( 128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 768 ) ( 768 -222 768 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 768 -222 443 ) ( 222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -222 768 ) ( 768 -128 768 ) ( 768 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 222 -128 443 ) ( 0 0 512 ) ( 128 -222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 222 -128 443 ) ( 222 -128 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 128 -222 768 ) ( 222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 768 ) ( 0 0 768 ) ( 222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 -443 0 ) ( 314 -314 256 ) ( 115 -428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 256 -443 0 ) ( 256 -768 0 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 256 ) ( 314 -768 256 ) ( 115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 256 ) ( 115 -768 256 ) ( 256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -768 256 ) ( 314 -768 256 ) ( 256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 -443 0 ) ( 768 -443 0 ) ( 768 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 -443 0 ) ( 256 -768 0 ) ( 768 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -443 0 ) ( 768 -768 0 ) ( 768 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -314 256 ) ( 768 -768 256 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 256 ) ( 314 -768 256 ) ( 256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 256 ) ( 768 -768 0 ) ( 256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 314 -314 256 ) ( 128 -222 443 ) ( 115 -428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 314 -314 256 ) ( 314 -768 256 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 128 -768 443 ) ( 115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 256 ) ( 115 -768 256 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -768 256 ) ( 128 -768 443 ) ( 314 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 314 -314 256 ) ( 768 -314 256 ) ( 768 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 -314 256 ) ( 314 -768 256 ) ( 768 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -314 256 ) ( 768 -768 256 ) ( 768 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -222 443 ) ( 768 -768 443 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 128 -768 443 ) ( 314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 443 ) ( 768 -768 256 ) ( 314 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 128 -222 443 ) ( 128 -768 443 ) ( 768 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 128 -222 768 ) ( 128 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -768 443 ) ( 128 -768 768 ) ( 768 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 443 ) ( 768 -768 768 ) ( 768 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -222 443 ) ( 768 -222 768 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 -768 768 ) ( 128 -768 768 ) ( 128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 -443 0 ) ( 115 -428 256 ) ( 0 -512 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 256 -443 0 ) ( 256 -768 0 ) ( 115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 256 ) ( 115 -768 256 ) ( 0 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -512 0 ) ( 0 -768 0 ) ( 256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -768 0 ) ( 115 -768 256 ) ( 256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 115 -428 256 ) ( 128 -222 443 ) ( 0 -256 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 115 -428 256 ) ( 115 -768 256 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 128 -768 443 ) ( 0 -256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -768 443 ) ( 115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -768 443 ) ( 128 -768 443 ) ( 115 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 128 -222 443 ) ( 128 -222 768 ) ( 0 -256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 443 ) ( 128 -768 443 ) ( 128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 -222 768 ) ( 128 -768 768 ) ( 0 -256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 768 ) ( 0 -768 768 ) ( 0 -256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -768 443 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -768 768 ) ( 128 -768 768 ) ( 128 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 128 -222 443 ) ( 0 0 512 ) ( 0 -256 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 128 -222 443 ) ( 128 -222 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( 0 -256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -256 768 ) ( 128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 768 ) ( 0 0 768 ) ( 128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 -512 0 ) ( 115 -428 256 ) ( -115 -428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 0 -512 0 ) ( 0 -768 0 ) ( 115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 -428 256 ) ( 115 -768 256 ) ( -115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( -115 -768 256 ) ( 0 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -768 256 ) ( 115 -768 256 ) ( 0 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 115 -428 256 ) ( 0 -256 443 ) ( -115 -428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 115 -428 256 ) ( 115 -768 256 ) ( 0 -256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -768 443 ) ( -115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( -115 -768 256 ) ( 115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -768 256 ) ( 0 -768 443 ) ( 115 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 768 0 ) ( 115 768 256 ) ( 0 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 256 768 0 ) ( 0 512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 115 768 256 ) ( 256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 512 0 ) ( 0 768 0 ) ( 115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 512 0 ) ( 115 428 256 ) ( 256 443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 128 768 443 ) ( 0 768 443 ) ( 115 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 768 443 ) ( 115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 768 443 ) ( 128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 115 768 256 ) ( 0 256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 0 256 443 ) ( 128 222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 128 768 768 ) ( 0 768 768 ) ( 0 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 768 443 ) ( 0 256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 768 ) ( 128 768 768 ) ( 128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 768 ) ( 0 768 768 ) ( 128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 768 443 ) ( 0 256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 256 768 ) ( 128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 128 222 768 ) ( 0 0 768 ) ( 0 256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 222 768 ) ( 0 256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( 128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 256 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 0 512 ) ( 128 222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 314 768 256 ) ( 115 768 256 ) ( 256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 314 768 256 ) ( 256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 115 768 256 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 256 768 0 ) ( 115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 115 428 256 ) ( 314 314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 314 768 256 ) ( 128 768 443 ) ( 115 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 314 768 256 ) ( 115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 768 443 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 115 768 256 ) ( 128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 128 222 443 ) ( 314 314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 443 768 0 ) ( 314 768 256 ) ( 256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 443 768 0 ) ( 256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 314 768 256 ) ( 443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 256 768 0 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 443 0 ) ( 314 314 256 ) ( 443 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 222 768 443 ) ( 128 768 443 ) ( 314 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 222 768 443 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 768 443 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 314 768 256 ) ( 128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 128 222 443 ) ( 222 128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 222 768 768 ) ( 128 768 768 ) ( 128 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 222 768 443 ) ( 128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 768 ) ( 222 768 768 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 768 ) ( 128 768 768 ) ( 222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 768 443 ) ( 128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 222 768 ) ( 222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 222 128 768 ) ( 0 0 768 ) ( 128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 222 128 768 ) ( 128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 128 222 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 128 222 443 ) ( 0 0 512 ) ( 222 128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 115 256 ) ( 768 314 256 ) ( 768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 768 115 256 ) ( 443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 768 314 256 ) ( 428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 768 256 0 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 314 314 256 ) ( 428 115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 768 256 ) ( 768 768 0 ) ( 768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 768 314 256 ) ( 443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 768 256 ) ( 768 768 256 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 768 0 ) ( 768 768 0 ) ( 314 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 768 256 0 ) ( 443 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 443 768 0 ) ( 314 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 768 115 256 ) ( 768 128 443 ) ( 768 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 768 115 256 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 768 128 443 ) ( 428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 768 314 256 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 222 128 443 ) ( 428 115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 768 443 ) ( 768 768 256 ) ( 768 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 768 128 443 ) ( 314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 768 443 ) ( 768 768 443 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 768 256 ) ( 768 768 256 ) ( 222 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 768 314 256 ) ( 314 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 314 314 256 ) ( 314 768 256 ) ( 222 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 768 768 768 ) ( 768 128 768 ) ( 222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 768 443 ) ( 222 768 768 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 768 443 ) ( 768 768 768 ) ( 222 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 768 128 443 ) ( 768 128 768 ) ( 768 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 222 128 768 ) ( 768 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 768 128 443 ) ( 768 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 768 0 0 ) ( 768 115 256 ) ( 768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 512 0 0 ) ( 768 0 0 ) ( 443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 768 115 256 ) ( 512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 768 256 0 ) ( 428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 443 256 0 ) ( 428 115 256 ) ( 512 0 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 0 443 ) ( 768 128 443 ) ( 768 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 443 ) ( 768 0 443 ) ( 428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 768 128 443 ) ( 256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 768 115 256 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 222 128 443 ) ( 256 0 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 0 768 ) ( 768 128 768 ) ( 768 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 443 ) ( 768 0 443 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 768 ) ( 768 0 768 ) ( 256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 768 ) ( 768 128 768 ) ( 256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 768 128 443 ) ( 222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 222 128 768 ) ( 256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 256 0 768 ) ( 0 0 768 ) ( 222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 443 ) ( 256 0 768 ) ( 222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( 256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 222 128 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 222 128 443 ) ( 0 0 512 ) ( 256 0 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 -115 256 ) ( 768 115 256 ) ( 768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 256 ) ( 768 -115 256 ) ( 512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 768 115 256 ) ( 428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 512 0 0 ) ( 768 0 0 ) ( 428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 512 0 0 ) ( 428 115 256 ) ( 428 -115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 768 -115 256 ) ( 768 0 443 ) ( 768 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 -115 256 ) ( 768 -115 256 ) ( 428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 256 0 443 ) ( 768 0 443 ) ( 428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 768 115 256 ) ( 256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 428 115 256 ) ( 256 0 443 ) ( 428 -115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -512 0 0 ) ( -428 115 256 ) ( -443 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -512 0 0 ) ( -768 0 0 ) ( -428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 256 ) ( -768 115 256 ) ( -443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 256 0 ) ( -768 256 0 ) ( -512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 256 0 ) ( -768 115 256 ) ( -768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -428 115 256 ) ( -256 0 443 ) ( -222 128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -428 115 256 ) ( -768 115 256 ) ( -256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 443 ) ( -768 0 443 ) ( -222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 443 ) ( -768 128 443 ) ( -428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 128 443 ) ( -768 0 443 ) ( -768 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 0 443 ) ( -256 0 768 ) ( -222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 443 ) ( -768 0 443 ) ( -256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 768 ) ( -768 0 768 ) ( -222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 768 ) ( -768 128 768 ) ( -222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 443 ) ( -768 128 443 ) ( -256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 128 768 ) ( -768 0 768 ) ( -768 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 0 443 ) ( 0 0 512 ) ( -222 128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -256 0 443 ) ( -256 0 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( -222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 443 ) ( -222 128 768 ) ( -256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 768 ) ( 0 0 768 ) ( -256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -443 256 0 ) ( -428 115 256 ) ( -314 314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -443 256 0 ) ( -768 256 0 ) ( -428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 256 ) ( -768 115 256 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 256 ) ( -768 314 256 ) ( -443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 314 256 ) ( -768 115 256 ) ( -768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -428 115 256 ) ( -222 128 443 ) ( -314 314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -428 115 256 ) ( -768 115 256 ) ( -222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 443 ) ( -768 128 443 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 256 ) ( -768 314 256 ) ( -428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 314 256 ) ( -768 128 443 ) ( -768 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -443 256 0 ) ( -314 314 256 ) ( -256 443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -443 256 0 ) ( -768 256 0 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 256 ) ( -768 314 256 ) ( -256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 443 0 ) ( -768 443 0 ) ( -443 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 443 0 ) ( -768 314 256 ) ( -768 256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -314 314 256 ) ( -222 128 443 ) ( -128 222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -314 314 256 ) ( -768 314 256 ) ( -222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 443 ) ( -768 128 443 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -768 222 443 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 222 443 ) ( -768 128 443 ) ( -768 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -222 128 443 ) ( -222 128 768 ) ( -128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 443 ) ( -768 128 443 ) ( -222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 128 768 ) ( -768 128 768 ) ( -128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 768 ) ( -768 222 768 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -768 222 443 ) ( -222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 222 768 ) ( -768 128 768 ) ( -768 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -222 128 443 ) ( 0 0 512 ) ( -128 222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -222 128 443 ) ( -222 128 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -128 222 768 ) ( -222 128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 768 ) ( 0 0 768 ) ( -222 128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 443 0 ) ( -314 314 256 ) ( -115 428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -256 443 0 ) ( -256 768 0 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 256 ) ( -314 768 256 ) ( -115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 256 ) ( -115 768 256 ) ( -256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 768 256 ) ( -314 768 256 ) ( -256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 443 0 ) ( -768 443 0 ) ( -768 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 443 0 ) ( -256 768 0 ) ( -768 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 443 0 ) ( -768 768 0 ) ( -768 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 314 256 ) ( -768 768 256 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 256 ) ( -314 768 256 ) ( -256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 256 ) ( -768 768 0 ) ( -256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -314 314 256 ) ( -128 222 443 ) ( -115 428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -314 314 256 ) ( -314 768 256 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -128 768 443 ) ( -115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 256 ) ( -115 768 256 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 768 256 ) ( -128 768 443 ) ( -314 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -314 314 256 ) ( -768 314 256 ) ( -768 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 314 256 ) ( -314 768 256 ) ( -768 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 314 256 ) ( -768 768 256 ) ( -768 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 222 443 ) ( -768 768 443 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -128 768 443 ) ( -314 314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 443 ) ( -768 768 256 ) ( -314 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -128 222 443 ) ( -128 768 443 ) ( -768 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -128 222 768 ) ( -128 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 768 443 ) ( -128 768 768 ) ( -768 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 443 ) ( -768 768 768 ) ( -768 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 222 443 ) ( -768 222 768 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 768 768 ) ( -128 768 768 ) ( -128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 443 0 ) ( -115 428 256 ) ( 0 512 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -256 443 0 ) ( -256 768 0 ) ( -115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 256 ) ( -115 768 256 ) ( 0 512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 512 0 ) ( 0 768 0 ) ( -256 443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 768 0 ) ( -115 768 256 ) ( -256 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -115 428 256 ) ( -128 222 443 ) ( 0 256 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -115 428 256 ) ( -115 768 256 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -128 768 443 ) ( 0 256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 768 443 ) ( -115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 768 443 ) ( -128 768 443 ) ( -115 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -128 222 443 ) ( -128 222 768 ) ( 0 256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 443 ) ( -128 768 443 ) ( -128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 222 768 ) ( -128 768 768 ) ( 0 256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 768 ) ( 0 768 768 ) ( 0 256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 768 443 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 768 768 ) ( -128 768 768 ) ( -128 768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -128 222 443 ) ( 0 0 512 ) ( 0 256 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -128 222 443 ) ( -128 222 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( 0 256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 256 768 ) ( -128 222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 768 ) ( 0 0 768 ) ( -128 222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 0 512 0 ) ( -115 428 256 ) ( 115 428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 0 512 0 ) ( 0 768 0 ) ( -115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 428 256 ) ( -115 768 256 ) ( 115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 115 768 256 ) ( 0 512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 768 256 ) ( -115 768 256 ) ( 0 768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -115 428 256 ) ( 0 256 443 ) ( 115 428 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( -115 428 256 ) ( -115 768 256 ) ( 0 256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 256 443 ) ( 0 768 443 ) ( 115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 428 256 ) ( 115 768 256 ) ( -115 428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 115 768 256 ) ( 0 768 443 ) ( -115 768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 -768 0 ) ( -115 -768 256 ) ( 0 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -256 -768 0 ) ( 0 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( -115 -768 256 ) ( -256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -512 0 ) ( 0 -768 0 ) ( -115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -512 0 ) ( -115 -428 256 ) ( -256 -443 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -128 -768 443 ) ( 0 -768 443 ) ( -115 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -768 443 ) ( -115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -768 443 ) ( -128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( -115 -768 256 ) ( 0 -256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( 0 -256 443 ) ( -128 -222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -128 -768 768 ) ( 0 -768 768 ) ( 0 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -768 443 ) ( 0 -256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 768 ) ( -128 -768 768 ) ( -128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 768 ) ( 0 -768 768 ) ( -128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -768 443 ) ( 0 -256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -256 768 ) ( -128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -128 -222 768 ) ( 0 0 768 ) ( 0 -256 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -222 768 ) ( 0 -256 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( -128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 -256 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 -256 443 ) ( 0 0 512 ) ( -128 -222 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -314 -768 256 ) ( -115 -768 256 ) ( -256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -314 -768 256 ) ( -256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( -115 -768 256 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -256 -768 0 ) ( -115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -115 -428 256 ) ( -314 -314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -314 -768 256 ) ( -128 -768 443 ) ( -115 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -314 -768 256 ) ( -115 -428 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -768 443 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( -115 -768 256 ) ( -128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -115 -428 256 ) ( -128 -222 443 ) ( -314 -314 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -443 -768 0 ) ( -314 -768 256 ) ( -256 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -443 -768 0 ) ( -256 -443 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -314 -768 256 ) ( -443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -256 -768 0 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 -443 0 ) ( -314 -314 256 ) ( -443 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -222 -768 443 ) ( -128 -768 443 ) ( -314 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -222 -768 443 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -768 443 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -314 -768 256 ) ( -128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -128 -222 443 ) ( -222 -128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -222 -768 768 ) ( -128 -768 768 ) ( -128 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -222 -768 443 ) ( -128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 768 ) ( -222 -768 768 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 768 ) ( -128 -768 768 ) ( -222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -768 443 ) ( -128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -222 768 ) ( -222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -222 -128 768 ) ( 0 0 768 ) ( -128 -222 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -222 -128 768 ) ( -128 -222 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( -128 -222 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -128 -222 443 ) ( 0 0 512 ) ( -222 -128 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -115 256 ) ( -768 -314 256 ) ( -768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -768 -115 256 ) ( -443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -768 -314 256 ) ( -428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -768 -256 0 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -314 -314 256 ) ( -428 -115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -768 256 ) ( -768 -768 0 ) ( -768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -768 -314 256 ) ( -443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -768 256 ) ( -768 -768 256 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -768 0 ) ( -768 -768 0 ) ( -314 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -768 -256 0 ) ( -443 -768 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -443 -768 0 ) ( -314 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -768 -115 256 ) ( -768 -128 443 ) ( -768 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -768 -115 256 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -768 -128 443 ) ( -428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -768 -314 256 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -222 -128 443 ) ( -428 -115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 -768 443 ) ( -768 -768 256 ) ( -768 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -768 -128 443 ) ( -314 -314 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -768 443 ) ( -768 -768 443 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -768 256 ) ( -768 -768 256 ) ( -222 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -768 -314 256 ) ( -314 -768 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -314 -314 256 ) ( -314 -768 256 ) ( -222 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -768 -768 768 ) ( -768 -128 768 ) ( -222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -768 443 ) ( -222 -768 768 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -768 443 ) ( -768 -768 768 ) ( -222 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -768 -128 443 ) ( -768 -128 768 ) ( -768 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -222 -128 768 ) ( -768 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -768 -128 443 ) ( -768 -768 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -768 0 0 ) ( -768 -115 256 ) ( -768 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -512 0 0 ) ( -768 0 0 ) ( -443 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -768 -115 256 ) ( -512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -768 -256 0 ) ( -428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -443 -256 0 ) ( -428 -115 256 ) ( -512 0 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 0 443 ) ( -768 -128 443 ) ( -768 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 443 ) ( -768 0 443 ) ( -428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -768 -128 443 ) ( -256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -768 -115 256 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -222 -128 443 ) ( -256 0 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 0 768 ) ( -768 -128 768 ) ( -768 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 443 ) ( -768 0 443 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 768 ) ( -768 0 768 ) ( -256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 768 ) ( -768 -128 768 ) ( -256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -768 -128 443 ) ( -222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -222 -128 768 ) ( -256 0 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( -256 0 768 ) ( 0 0 768 ) ( -222 -128 768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 443 ) ( -256 0 768 ) ( -222 -128 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 0 0 512 ) ( 0 0 768 ) ( -256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( -222 -128 768 ) ( 0 0 512 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -222 -128 443 ) ( 0 0 512 ) ( -256 0 443 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 115 256 ) ( -768 -115 256 ) ( -768 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 256 ) ( -768 115 256 ) ( -512 0 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -768 -115 256 ) ( -428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -512 0 0 ) ( -768 0 0 ) ( -428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -512 0 0 ) ( -428 -115 256 ) ( -428 115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( -768 115 256 ) ( -768 0 443 ) ( -768 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 115 256 ) ( -768 115 256 ) ( -428 -115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -256 0 443 ) ( -768 0 443 ) ( -428 115 256 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -768 -115 256 ) ( -256 0 443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( -428 -115 256 ) ( -256 0 443 ) ( -428 115 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +} +{ +( 1024 768 -768 ) ( 768 768 -768 ) ( 768 -128 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 832 -128 768 ) ( 832 768 768 ) ( 1088 768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 704 -768 0 ) ( 960 -768 0 ) ( 960 -768 -256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 776 -128 0 ) ( 776 768 0 ) ( 776 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 1024 768 0 ) ( 768 768 0 ) ( 768 768 -256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 768 0 ) ( 768 -128 0 ) ( 768 -128 -256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 768 -768 -768 ) ( -768 -768 -768 ) ( -768 -832 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 -832 768 ) ( -768 -768 768 ) ( 768 -768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 -776 760 ) ( 768 -776 760 ) ( 768 -776 -776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 -832 768 ) ( 768 -768 768 ) ( 768 -768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 -768 768 ) ( -768 -768 768 ) ( -768 -768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 -768 768 ) ( -768 -832 768 ) ( -768 -832 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -768 768 -768 ) ( -1088 768 -768 ) ( -1088 -768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -1088 -768 768 ) ( -1088 768 768 ) ( -768 768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -1088 -768 768 ) ( -768 -768 768 ) ( -768 -768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 -768 768 ) ( -768 768 768 ) ( -768 768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 768 768 ) ( -1088 768 768 ) ( -1088 768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -776 704 768 ) ( -776 -832 768 ) ( -776 -832 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 768 1088 -768 ) ( -768 1088 -768 ) ( -768 768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 768 768 ) ( -768 1088 768 ) ( 768 1088 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 768 768 ) ( 768 768 768 ) ( 768 768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 768 768 ) ( 768 1088 768 ) ( 768 1088 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 832 776 768 ) ( -704 776 768 ) ( -704 776 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 1088 768 ) ( -768 768 768 ) ( -768 768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 768 768 768 ) ( -768 768 768 ) ( -768 -768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -744 -704 776 ) ( -744 832 776 ) ( 792 832 776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 -768 1344 ) ( 768 -768 1344 ) ( 768 -768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 -768 1344 ) ( 768 768 1344 ) ( 768 768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 768 1344 ) ( -768 768 1344 ) ( -768 768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 768 1344 ) ( -768 -768 1344 ) ( -768 -768 768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 768 768 -776 ) ( -768 768 -776 ) ( -768 -768 -776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 -768 -768 ) ( -768 768 -768 ) ( 768 768 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 -768 -768 ) ( 768 -768 -768 ) ( 768 -768 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 -768 -768 ) ( 768 768 -768 ) ( 768 768 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 768 768 -768 ) ( -768 768 -768 ) ( -768 768 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 768 -768 ) ( -768 -768 -768 ) ( -768 -768 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"origin" "-64 0 -384" +"classname" "info_player_deathmatch" +} +{ +"light" "1000" +"origin" "0 0 64" +"classname" "light" +} diff --git a/regression_tests/q3map2/coarse_snap_normal/README.txt b/regression_tests/q3map2/coarse_snap_normal/README.txt new file mode 100644 index 0000000..6525380 --- /dev/null +++ b/regression_tests/q3map2/coarse_snap_normal/README.txt @@ -0,0 +1,16 @@ +DESCRIPTION OF PROBLEM: +======================= + +Because of the coarse nature of SnapNormal(), planes that are 0.25 degrees +away from being axial are "snapped" to be axial. The "normal epsilon" +is a very small value by default, and cannot go much smaller (without +running into limits of floating point numbers). The problem with +SnapNormal() is that we compare the components of the normal that are near +1, instead of comaring the components that are near 0. This leads to a very +coarse and inaccurate SnapNormal(). + +If you open the example map in Radiant, you can see that the red brick should +touch the middle checkered brick at the edge. However, once this map is +compiled, the edges are a significant distance apart. This is due to the +coarse and inaccurate nature of SnapNormal(). Likewise, the green brick should +be flush with the center tiled brick, but it's not. diff --git a/regression_tests/q3map2/coarse_snap_normal/maps/coarse_snap_normal.map b/regression_tests/q3map2/coarse_snap_normal/maps/coarse_snap_normal.map new file mode 100644 index 0000000..9be4523 --- /dev/null +++ b/regression_tests/q3map2/coarse_snap_normal/maps/coarse_snap_normal.map @@ -0,0 +1,84 @@ +{ +"classname" "worldspawn" +{ +( 544 312 -8 ) ( -584 312 -8 ) ( -584 -488 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -576 -488 0 ) ( -576 312 0 ) ( 552 312 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -584 -512 8 ) ( 544 -512 8 ) ( 544 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -488 8 ) ( 512 312 8 ) ( 512 312 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 528 512 8 ) ( -600 512 8 ) ( -600 512 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 312 8 ) ( -512 -488 8 ) ( -512 -488 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 720 512 0 ) ( 512 512 0 ) ( 512 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -512 512 ) ( 512 512 512 ) ( 720 512 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -512 512 ) ( 720 -512 512 ) ( 720 -512 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 520 -512 520 ) ( 520 512 520 ) ( 520 512 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 720 512 512 ) ( 512 512 512 ) ( 512 512 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 512 512 ) ( 512 -512 512 ) ( 512 -512 -8 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( -512 512 0 ) ( -592 512 0 ) ( -592 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -592 -512 512 ) ( -592 512 512 ) ( -512 512 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -592 -512 512 ) ( -512 -512 512 ) ( -512 -512 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -512 512 ) ( -512 512 512 ) ( -512 512 -8 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -512 512 512 ) ( -592 512 512 ) ( -592 512 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -520 512 536 ) ( -520 -512 536 ) ( -520 -512 16 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 512 512 512 ) ( -512 512 512 ) ( -512 -512 512 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -488 -512 520 ) ( -488 512 520 ) ( 536 512 520 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -512 664 ) ( 512 -512 664 ) ( 512 -512 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -512 664 ) ( 512 512 664 ) ( 512 512 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 512 664 ) ( -512 512 664 ) ( -512 512 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 512 664 ) ( -512 -512 664 ) ( -512 -512 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 512 608 0 ) ( -512 608 0 ) ( -512 520 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 520 512 ) ( -512 608 512 ) ( 512 608 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 512 512 ) ( 512 512 512 ) ( 512 512 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 512 520 512 ) ( 512 608 512 ) ( 512 608 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 520 520 ) ( -512 520 520 ) ( -512 520 8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 608 512 ) ( -512 520 512 ) ( -512 520 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 512 -512 0 ) ( -512 -512 0 ) ( -512 -600 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -600 512 ) ( -512 -512 512 ) ( 512 -512 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -520 520 ) ( 512 -520 520 ) ( 512 -520 8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -600 512 ) ( 512 -512 512 ) ( 512 -512 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -512 512 ) ( -512 -512 512 ) ( -512 -512 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -512 -512 512 ) ( -512 -600 512 ) ( -512 -600 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 512 72 64 ) ( -512 72 64 ) ( -512 -448 64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -128 448 260 ) ( 192 448 260 ) ( 192 -448 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -512 -448 256 ) ( 512 -448 256 ) ( 512 -448 56 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 192 -440 256 ) ( 192 80 256 ) ( 192 80 56 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 192 448 260 ) ( -128 448 260 ) ( -128 448 64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -128 56 256 ) ( -128 -464 256 ) ( -128 -464 56 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 192 -448 232 ) ( -128 -448 232 ) ( -128 -472 232 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( -128 -472 256 ) ( -128 -448 256 ) ( 192 -448 256 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( -128 -472 256 ) ( 192 -472 256 ) ( 192 -472 232 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 192 -472 256 ) ( 192 -448 256 ) ( 192 -448 232 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 192 -448 256 ) ( -128 -448 256 ) ( -128 -448 232 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( -128 -448 256 ) ( -128 -472 256 ) ( -128 -472 232 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 192 472 260 ) ( -128 472 260 ) ( -128 448 260 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( -128 448 280 ) ( -128 472 280 ) ( 192 472 280 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( -128 448 280 ) ( 192 448 280 ) ( 192 448 256 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( 192 448 280 ) ( 192 472 280 ) ( 192 472 256 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( 192 472 280 ) ( -128 472 280 ) ( -128 472 256 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( -128 472 280 ) ( -128 448 280 ) ( -128 448 256 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +} +} +{ +"light" "3000" +"origin" "0 0 384" +"classname" "light" +} +{ +"origin" "0 -64 384" +"classname" "info_player_deathmatch" +} diff --git a/regression_tests/q3map2/decal_misalignment/README.txt b/regression_tests/q3map2/decal_misalignment/README.txt new file mode 100644 index 0000000..f9aa3d6 --- /dev/null +++ b/regression_tests/q3map2/decal_misalignment/README.txt @@ -0,0 +1,16 @@ +DESCRIPTION OF PROBLEM: +======================= + +The info_null in the map for the decal is not 100% below the center of the +decal itself, because to be totally below it would have to lie on half-units. +So, the info_null lies almost directly below the center of the decal. In +this particular case, all kinds of bad things happen to the decal. For one, +during compiling we get warnings like this: + + Bad texture matrix! (B) (50.512253, -49.515625) != (50.484375, -49.515625) + Bad texture matrix! (C) (48.723190, -49.522587) != (48.695312, -49.515625) + Bad texture matrix! (B) (48.723186, -49.522587) != (48.695312, -49.515625) + +If you look at where the decal (it's just a blue translucent tile texture) +meets the far wall, it's clearly not aligned correctly. The tile on the decal +and the tile on the wall should align perfectly, and it's quite a bit off. diff --git a/regression_tests/q3map2/decal_misalignment/maps/decal_misalignment.map b/regression_tests/q3map2/decal_misalignment/maps/decal_misalignment.map new file mode 100644 index 0000000..b764bf2 --- /dev/null +++ b/regression_tests/q3map2/decal_misalignment/maps/decal_misalignment.map @@ -0,0 +1,89 @@ +{ +"classname" "worldspawn" +{ +( 6472 6336 -960 ) ( 6288 6336 -960 ) ( 6288 6320 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6264 6320 -576 ) ( 6264 6336 -576 ) ( 6448 6336 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6280 6328 -832 ) ( 6464 6328 -832 ) ( 6464 6328 -848 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 6320 -832 ) ( 6464 6336 -832 ) ( 6464 6336 -848 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 6336 -832 ) ( 6280 6336 -832 ) ( 6280 6336 -848 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 6336 -832 ) ( 6144 6320 -832 ) ( 6144 6320 -848 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6552 6520 -960 ) ( 6464 6520 -960 ) ( 6464 6336 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 6336 -576 ) ( 6464 6520 -576 ) ( 6552 6520 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 6336 -576 ) ( 6552 6336 -576 ) ( 6552 6336 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6472 6344 -576 ) ( 6472 6528 -576 ) ( 6472 6528 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6528 7360 -576 ) ( 6440 7360 -576 ) ( 6440 7360 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 6520 -576 ) ( 6464 6336 -576 ) ( 6464 6336 -960 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 6464 7424 -960 ) ( 6144 7424 -960 ) ( 6144 7360 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 7360 -576 ) ( 6144 7424 -576 ) ( 6464 7424 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 7360 -576 ) ( 6464 7360 -576 ) ( 6464 7360 -960 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 6464 7360 -576 ) ( 6464 7424 -576 ) ( 6464 7424 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 7368 -576 ) ( 6144 7368 -576 ) ( 6144 7368 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 7424 -576 ) ( 6144 7360 -576 ) ( 6144 7360 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6144 7360 -960 ) ( 5880 7360 -960 ) ( 5880 6808 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 5880 6808 -576 ) ( 5880 7360 -576 ) ( 6144 7360 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 5864 6336 -576 ) ( 6128 6336 -576 ) ( 6128 6336 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 6808 -576 ) ( 6144 7360 -576 ) ( 6144 7360 -960 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 7360 -576 ) ( 5880 7360 -576 ) ( 5880 7360 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6136 7384 -576 ) ( 6136 6832 -576 ) ( 6136 6832 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6456 7360 -968 ) ( 6136 7360 -968 ) ( 6136 6336 -968 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 6336 -960 ) ( 6144 7360 -960 ) ( 6464 7360 -960 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 6336 -960 ) ( 6464 6336 -960 ) ( 6464 6336 -1008 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 6336 -960 ) ( 6464 7360 -960 ) ( 6464 7360 -1008 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 7360 -960 ) ( 6144 7360 -960 ) ( 6144 7360 -1008 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 7360 -960 ) ( 6144 6336 -960 ) ( 6144 6336 -1008 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6464 7360 -576 ) ( 6152 7360 -576 ) ( 6152 6336 -576 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 6176 6336 -568 ) ( 6176 7360 -568 ) ( 6488 7360 -568 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6152 6336 -472 ) ( 6464 6336 -472 ) ( 6464 6336 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 6336 -472 ) ( 6464 7360 -472 ) ( 6464 7360 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6464 7360 -472 ) ( 6152 7360 -472 ) ( 6152 7360 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 7360 -472 ) ( 6144 6336 -472 ) ( 6144 6336 -576 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6200 7360 -896 ) ( 6200 6336 -896 ) ( 6592 6336 -896 ) radiant_regression_tests/glass 0 0 0 0.500000 0.500000 134217728 257 0 +( 6360 6336 -832 ) ( 6360 7256 -832 ) ( 6464 7256 -832 ) radiant_regression_tests/glass 0 0 0 0.500000 0.500000 134217728 257 0 +( 6208 6337 -832 ) ( 6400 6337 -832 ) ( 6400 6337 -896 ) radiant_regression_tests/glass 0 0 0 0.500000 0.500000 134217728 257 0 +( 6463 6336 -816 ) ( 6463 7360 -816 ) ( 6463 7360 -880 ) radiant_regression_tests/glass 0 0 0 0.500000 0.500000 134217728 257 0 +( 6232 7264 -896 ) ( 6232 7264 -832 ) ( 6232 6337 -896 ) radiant_regression_tests/glass 0 0 0 0.500000 0.500000 134217728 257 0 +( 6232 7256 -832 ) ( 6232 7256 -896 ) ( 6463 7256 -832 ) radiant_regression_tests/glass 0 0 0 0.500000 0.500000 134217728 257 0 +} +} +{ +"target" "checker" +"classname" "_decal" +{ +patchDef2 +{ +radiant_regression_tests/tile_trans +( 3 3 0 0 0 ) +( +( ( 6462 7255 -832 50.484375 -56.679688 ) ( 6462 6796.500000 -832 50.484375 -53.097656 ) ( 6462 6338 -832 50.484375 -49.515625 ) ) +( ( 6347.500000 7255 -832 49.589844 -56.679688 ) ( 6347.500000 6796.500000 -832 49.589844 -53.097656 ) ( 6347.500000 6338 -832 49.589844 -49.515625 ) ) +( ( 6233 7255 -832 48.695312 -56.679688 ) ( 6233 6796.500000 -832 48.695312 -53.097656 ) ( 6233 6338 -832 48.695312 -49.515625 ) ) +) +} +} +} +{ +"targetname" "checker" +"origin" "6347 6796 -840" +"classname" "info_null" +} +{ +"origin" "6288 6704 -712" +"classname" "info_player_deathmatch" +} +{ +"light" "2000" +"origin" "6304 6792 -704" +"classname" "light" +} diff --git a/regression_tests/q3map2/decal_misalignment/scripts/radiant_regression_tests.shader b/regression_tests/q3map2/decal_misalignment/scripts/radiant_regression_tests.shader new file mode 100644 index 0000000..af31c07 --- /dev/null +++ b/regression_tests/q3map2/decal_misalignment/scripts/radiant_regression_tests.shader @@ -0,0 +1,29 @@ +textures/radiant_regression_tests/tile_trans +{ + qer_trans 0.9 + q3map_bounceScale 0.0 + surfaceparm trans + cull disable + polygonOffset + { + map textures/radiant_regression_tests/tile_trans.tga + blendFunc blend + alphaGen const 1 + } +} + +textures/radiant_regression_tests/glass +{ + qer_editorImage textures/radiant_regression_tests/qer_glass.tga + qer_trans 0.6 + q3map_bounceScale 0.0 + surfaceparm nolightmap + surfaceparm detail + surfaceparm trans + cull disable + { + map textures/radiant_regression_tests/glass.tga + blendFunc add + tcGen environment + } +} diff --git a/regression_tests/q3map2/degenerate_winding/README.txt b/regression_tests/q3map2/degenerate_winding/README.txt new file mode 100644 index 0000000..b060d08 --- /dev/null +++ b/regression_tests/q3map2/degenerate_winding/README.txt @@ -0,0 +1,4 @@ +DESCRIPTION OF PROBLEM: +======================= + +I'm testing when windings become degenerate. diff --git a/regression_tests/q3map2/degenerate_winding/maps/degenerate_winding.map b/regression_tests/q3map2/degenerate_winding/maps/degenerate_winding.map new file mode 100644 index 0000000..808de18 --- /dev/null +++ b/regression_tests/q3map2/degenerate_winding/maps/degenerate_winding.map @@ -0,0 +1,68 @@ +{ +"classname" "worldspawn" +{ +( 256 256 -8 ) ( -256 256 -8 ) ( -256 -264 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -264 0 ) ( -256 256 0 ) ( 256 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -256 -256 8 ) ( 256 -256 8 ) ( 256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -264 8 ) ( 256 256 8 ) ( 256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 256 8 ) ( -256 256 8 ) ( -256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 8 ) ( -256 -264 8 ) ( -256 -264 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 296 256 0 ) ( 256 256 0 ) ( 256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 280 -256 512 ) ( 280 256 512 ) ( 320 256 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 392 ) ( 296 -256 392 ) ( 296 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 264 -256 384 ) ( 264 256 384 ) ( 264 256 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 296 256 392 ) ( 256 256 392 ) ( 256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 256 392 ) ( 256 -256 392 ) ( 256 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( -256 256 0 ) ( -320 256 0 ) ( -320 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -320 -256 512 ) ( -320 256 512 ) ( -256 256 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -320 -256 504 ) ( -256 -256 504 ) ( -256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -256 504 ) ( -256 256 504 ) ( -256 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -256 256 504 ) ( -320 256 504 ) ( -320 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -264 256 496 ) ( -264 -256 496 ) ( -264 -256 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 256 256 512 ) ( -256 256 512 ) ( -256 -256 512 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -224 -256 520 ) ( -224 256 520 ) ( 288 256 520 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -256 584 ) ( 256 -256 584 ) ( 256 -256 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 584 ) ( 256 256 584 ) ( 256 256 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 256 584 ) ( -256 256 584 ) ( -256 256 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 584 ) ( -256 -256 584 ) ( -256 -256 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 256 328 0 ) ( -256 328 0 ) ( -256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 512 ) ( -256 328 512 ) ( 256 328 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 512 ) ( 256 256 512 ) ( 256 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 256 256 512 ) ( 256 328 512 ) ( 256 328 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 264 536 ) ( -256 264 536 ) ( -256 264 24 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 328 512 ) ( -256 256 512 ) ( -256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 256 -256 0 ) ( -256 -256 0 ) ( -256 -304 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -304 512 ) ( -256 -256 512 ) ( 256 -256 512 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -264 520 ) ( 256 -264 520 ) ( 256 -264 8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -304 512 ) ( 256 -256 512 ) ( 256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 512 ) ( -256 -256 512 ) ( -256 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -256 -256 512 ) ( -256 -304 512 ) ( -256 -304 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 64 0 128 ) ( 8 0 128 ) ( 8 -8 128 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( 8 -8 464 ) ( 8 0 464 ) ( 64 0 464 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( -240 8 464 ) ( -240 0 464 ) ( -240 0 160 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( -240 24 128 ) ( 160 8 128 ) ( -240 24 464 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 160 8 128 ) ( -240 0 128 ) ( 160 8 464 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 159 11 128 ) ( 159 0 128 ) ( 159 11 464 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +} +} +{ +"light" "1000" +"origin" "-40 0 32" +"classname" "light" +} +{ +"origin" "-40 128 96" +"classname" "info_player_deathmatch" +} diff --git a/regression_tests/q3map2/degenerate_winding2/README.txt b/regression_tests/q3map2/degenerate_winding2/README.txt new file mode 100644 index 0000000..b36f2db --- /dev/null +++ b/regression_tests/q3map2/degenerate_winding2/README.txt @@ -0,0 +1,14 @@ +DESCRIPTION OF PROBLEM: +======================= + +The sample map contains a wedge brush. The tip (the sharp edge) of the wedge +is chopped off by 2 planes, leaving very narrow windings. Each of these 2 +narrow windings is less than 0.1 units tall. However, the wedge has height +exactly equal to 1/8 unit at the point where it is chopped. Therefore, +the two narrow sides caused by the chops are expected to be degenerate and the +top face of the wedge is expected to be unaffected. This should leave a +"hole" in the narrow part of the wedge. + +The hole isn't desirable but it's expected based on the logic in the code. +Still, if there is a hole in the brush, I consider this regression test to +be broken. diff --git a/regression_tests/q3map2/degenerate_winding2/maps/degenerate_winding2.map b/regression_tests/q3map2/degenerate_winding2/maps/degenerate_winding2.map new file mode 100644 index 0000000..70abf7c --- /dev/null +++ b/regression_tests/q3map2/degenerate_winding2/maps/degenerate_winding2.map @@ -0,0 +1,69 @@ +{ +"classname" "worldspawn" +{ +( 7 0 0 ) ( 7 0 1 ) ( 7 64 1 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 7 0 0 ) ( 7 64 0 ) ( -1 64 0 ) radiant_regression_tests/blue 0 0 0 0.500000 0.500000 0 0 0 +( 7 0 1 ) ( 7 0 0 ) ( -1 0 0 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( -1 64 0 ) ( 7 64 0 ) ( 7 64 1 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 7 0 1 ) ( -1 0 0 ) ( -1 64 0 ) radiant_regression_tests/blue 0 0 0 0.500000 0.500000 0 0 0 +( -1 0 2 ) ( 0 0 0 ) ( 0 64 0 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +( 1 0 2 ) ( -7 0 -13 ) ( -7 64 -13 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 128 72 -72 ) ( -128 72 -72 ) ( -128 8 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 120 -64 ) ( -128 184 -64 ) ( 128 184 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -128 -128 -64 ) ( 128 -128 -64 ) ( 128 -128 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 0 -64 ) ( 192 64 -64 ) ( 192 64 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 128 192 -64 ) ( -128 192 -64 ) ( -128 192 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 64 -64 ) ( -192 0 -64 ) ( -192 0 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 256 192 -64 ) ( 192 192 -64 ) ( 192 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 256 ) ( 192 192 256 ) ( 256 192 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 256 ) ( 256 -128 256 ) ( 256 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 200 -128 240 ) ( 200 192 240 ) ( 200 192 -80 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 192 256 ) ( 192 192 256 ) ( 192 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 192 256 ) ( 192 -128 256 ) ( 192 -128 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( -192 192 -64 ) ( -280 192 -64 ) ( -280 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -280 -128 256 ) ( -280 192 256 ) ( -192 192 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -280 -128 256 ) ( -192 -128 256 ) ( -192 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -128 256 ) ( -192 192 256 ) ( -192 192 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -192 192 256 ) ( -280 192 256 ) ( -280 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -200 192 256 ) ( -200 -128 256 ) ( -200 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 192 192 256 ) ( -192 192 256 ) ( -192 -128 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -176 -128 264 ) ( -176 192 264 ) ( 208 192 264 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -128 304 ) ( 192 -128 304 ) ( 192 -128 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 304 ) ( 192 192 304 ) ( 192 192 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 192 304 ) ( -192 192 304 ) ( -192 192 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 192 304 ) ( -192 -128 304 ) ( -192 -128 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 192 272 -64 ) ( -192 272 -64 ) ( -192 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 192 256 ) ( -192 272 256 ) ( 192 272 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 192 256 ) ( 192 192 256 ) ( 192 192 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 192 192 256 ) ( 192 272 256 ) ( 192 272 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 200 256 ) ( -192 200 256 ) ( -192 200 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 272 256 ) ( -192 192 256 ) ( -192 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 192 -128 -64 ) ( -192 -128 -64 ) ( -192 -192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -192 256 ) ( -192 -128 256 ) ( 192 -128 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -136 280 ) ( 192 -136 280 ) ( 192 -136 -40 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -192 256 ) ( 192 -128 256 ) ( 192 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 256 ) ( -192 -128 256 ) ( -192 -128 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -192 -128 256 ) ( -192 -192 256 ) ( -192 -192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"light" "1000" +"origin" "-8 40 128" +"classname" "light" +} +{ +"origin" "-112 -40 40" +"classname" "info_player_deathmatch" +} diff --git a/regression_tests/q3map2/degenerate_winding3/README.txt b/regression_tests/q3map2/degenerate_winding3/README.txt new file mode 100644 index 0000000..3aa08fc --- /dev/null +++ b/regression_tests/q3map2/degenerate_winding3/README.txt @@ -0,0 +1,18 @@ +DESCRIPTION OF PROBLEM: +======================= + +The sample map contains a wedge brush. The tip (the sharp edge) of the wedge +is chopped off by the YZ plane (side 5, or the last side, of the brush). +The height of the wedge where it is chopped is about 0.9. This makes it +barely smaller than DEGENERATE_EPSILON. So the face resulting from the chop +is probably degenerate, and that winding will be removed. I'm now wondering +what happens to the rest of the brush. 0.9 rounded to the nearest 1/8 unit +is 1/8, so the top face of the brush should get a slight raise, making the +"hole" even bigger. The sides will have degenerate edges near the chop, so +they will become triangles, creating open slivers in the sides. + +Although this behavior is a tad nasty, it is expected based on the way the +code is written. I want to make sure nothing really nasty happens. + +I consider this regression test to be broken if there is a "hole" in the brush, +and I consider this test to be very broken if something more drastic happens. diff --git a/regression_tests/q3map2/degenerate_winding3/maps/degenerate_winding3.map b/regression_tests/q3map2/degenerate_winding3/maps/degenerate_winding3.map new file mode 100644 index 0000000..1817265 --- /dev/null +++ b/regression_tests/q3map2/degenerate_winding3/maps/degenerate_winding3.map @@ -0,0 +1,68 @@ +{ +"classname" "worldspawn" +{ +( 10 0 0 ) ( 10 0 1 ) ( 10 64 1 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 10 0 0 ) ( 10 64 0 ) ( -1 64 0 ) radiant_regression_tests/blue 0 0 0 0.500000 0.500000 0 0 0 +( 10 0 1 ) ( 10 0 0 ) ( -1 0 0 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( -1 64 0 ) ( 10 64 0 ) ( 10 64 1 ) radiant_regression_tests/green 0 0 0 0.500000 0.500000 0 0 0 +( 10 0 1 ) ( -1 0 0 ) ( -1 64 0 ) radiant_regression_tests/blue 0 0 0 0.500000 0.500000 0 0 0 +( 0 0 1 ) ( 0 0 0 ) ( 0 64 0 ) radiant_regression_tests/red 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 128 72 -72 ) ( -128 72 -72 ) ( -128 8 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 120 -64 ) ( -128 184 -64 ) ( 128 184 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -128 -128 -64 ) ( 128 -128 -64 ) ( 128 -128 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 0 -64 ) ( 192 64 -64 ) ( 192 64 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 128 192 -64 ) ( -128 192 -64 ) ( -128 192 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 64 -64 ) ( -192 0 -64 ) ( -192 0 -72 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 256 192 -64 ) ( 192 192 -64 ) ( 192 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 256 ) ( 192 192 256 ) ( 256 192 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 256 ) ( 256 -128 256 ) ( 256 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 200 -128 240 ) ( 200 192 240 ) ( 200 192 -80 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 192 256 ) ( 192 192 256 ) ( 192 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 192 256 ) ( 192 -128 256 ) ( 192 -128 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( -192 192 -64 ) ( -280 192 -64 ) ( -280 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -280 -128 256 ) ( -280 192 256 ) ( -192 192 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -280 -128 256 ) ( -192 -128 256 ) ( -192 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -128 256 ) ( -192 192 256 ) ( -192 192 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -192 192 256 ) ( -280 192 256 ) ( -280 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -200 192 256 ) ( -200 -128 256 ) ( -200 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 192 192 256 ) ( -192 192 256 ) ( -192 -128 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -176 -128 264 ) ( -176 192 264 ) ( 208 192 264 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -128 304 ) ( 192 -128 304 ) ( 192 -128 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 304 ) ( 192 192 304 ) ( 192 192 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 192 304 ) ( -192 192 304 ) ( -192 192 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 192 304 ) ( -192 -128 304 ) ( -192 -128 248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 192 272 -64 ) ( -192 272 -64 ) ( -192 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 192 256 ) ( -192 272 256 ) ( 192 272 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 192 256 ) ( 192 192 256 ) ( 192 192 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 192 192 256 ) ( 192 272 256 ) ( 192 272 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 200 256 ) ( -192 200 256 ) ( -192 200 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 272 256 ) ( -192 192 256 ) ( -192 192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 192 -128 -64 ) ( -192 -128 -64 ) ( -192 -192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -192 256 ) ( -192 -128 256 ) ( 192 -128 256 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -192 -136 280 ) ( 192 -136 280 ) ( 192 -136 -40 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -192 256 ) ( 192 -128 256 ) ( 192 -128 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 192 -128 256 ) ( -192 -128 256 ) ( -192 -128 -64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -192 -128 256 ) ( -192 -192 256 ) ( -192 -192 -64 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"light" "1000" +"origin" "-8 40 128" +"classname" "light" +} +{ +"origin" "-112 -40 40" +"classname" "info_player_deathmatch" +} diff --git a/regression_tests/q3map2/disappearing_sliver/README.txt b/regression_tests/q3map2/disappearing_sliver/README.txt new file mode 100644 index 0000000..a65d920 --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver/README.txt @@ -0,0 +1,183 @@ +DESCRIPTION OF PROBLEM: +======================= + +The example map, maps/disappearing_sliver.map, contains an example of this bug. +There are 6 walls in the map, and one tall thin triangular sliver brush in the +middle of the room (7 brushes total). Only one face of the sliver in the +middle of the room is a draw surface. The bug is that this sliver surface is +not rendered in the compiled BSP. Note that the sliver brush was hand-crafted +to demonstrate the bug. If you re-save the map, Radiant might adjust the +order in which the planes on the brush are defined, and this might, as a side +effect, get rid of the immediate bug. + +To trigger the bug, compile the map; you don't need -vis or -light. Only +-bsp (the first q3map2 stage) is necessary to trigger the bug. The only +entities in the map are 2 lights and a single info_player_deathmatch, so the +map will compile for any Q3 mod. + + +SOLUTION TO PROBLEM: +==================== + +Several days were spent studying this problem in great detail. + +The fix for this problem was to make the outcome of the VectorNormalize() +function libs/mathlib/mathlib.c more accurate. The previous code in this +function looks something like this: + + vec_t length, ilength; // vec_t is a typedef for 32 bit float. + + /* Compute length */ + + ilength = 1.0f/length; + out[0] = in[0]*ilength; + out[1] = in[1]*ilength; + out[2] = in[2]*ilength; + +As you can see, we are introducing a lot of extra error into our normalized +vector by multiplying by the reciprocal length instead of outright dividing +by the length. The new fixed code looks like this: + + out[0] = in[0]/length; + out[1] = in[1]/length; + out[2] = in[2]/length; + +And we get rid of the recpirocal length ilength altogether. Even the +slightest math errors are magnified in successive calls to linear algebra +functions. + +The change described above was commmitted to GtkRadiant trunk as revision 363. + + +POSSIBLE SIDE EFFECTS: +====================== + +The only negative side effect is that compilation of a map might take longer +due to an increased number of divide operations. (I'm actually not sure if +that is indeed the case.) Another side effect might be that if you're used +to a map being broken (missing triangles) or having "sparklies" between +brushes, those might be gone now. :-) + + +IN-DEPTH DISCUSSION: +==================== + +VectorNormalize() is used very frequently in Radiant and tools code. My goal +for this fix was to make the least amount of code change but still be able to +demonstrate a significant improvement in math accuracy (including a fix to +the test case). At the same time don't risk that any other bugs pop up as a +side effect of this change. + +Here is the sequence of calls (stack trace) that cause the example bug to +happen: + + main() in main.c --> + BSPMain() in bsp.c --> + LoadMapFile() in map.c --> + ParseMapEntity() in map.c --> + ParseBrush() in map.c --> + FinishBrush() in map.c --> + CreateBrushWindings() in brush.c --> + ChopWindingInPlace() in polylib.c + +What basically happens in this sequence of calls is that a brush is parsed +out of the map file, "infinite" planes are created for each brush face, and +then the planes are "intersected" to find the exact vertex topologies of each +brush face. The vertex topology of the visible face of the sliver (in the +example map) gets computed with a significant amount of math error. If we +did our math with infinite precision, the sliver face would have the following +vertices: + + (67 -1022 0) + (88 -892 -768) + (134 -1015 0) + +In fact, if you open the map file (disappearing_sliver.map), you can actually +see these exact points embedded in the third plane defined on brush 0. + +I managed to print out the actual computed vertices of the sliver face before +and after this bug fix. Basically this is printed out after all the +ChopWindingInPlace() calls in the above stack trace: + + (66.984695 -1021.998657 0.000000) + (87.989571 -891.969116 -768.174316) + (133.998917 -1014.997314 0.000000) + +(If you want to print this out for yourself, use winding_logging.patch.) + +The same vertices after the bugfix have the following coordinates: + + (67.000229 -1021.998657 0.000000) + (88.000175 -891.999146 -767.997437) + (133.999146 -1014.998779 0.000000) + +As you can see, the vertices after the fix are substantially more accurate, +and all it took was an improvement to VectorNormalize(). + +The problem before the fix was the Z coordinate of the second point, namely +-768.174316. There is a lot of "snap to nearest 1/8 unit" and "epsilon 0.1" +code used throughout q3map2. 0.174 is greater than the 0.1 epsilon, and that +is the problem. + + main() in main.c --> + BSPMain() in bsp.c --> + ProcessModels() in bsp.c --> + ProcessWorldModel() in bsp.c --> + ClipSidesIntoTree() in surface.c --> + ClipSideIntoTree_r() in surface.c --> + ClipWindingEpsilon() in polylib.c + +Now what ClipWindingEpsilon() does is, since -768.174316 reaches below the +plane z = -768 (and over the 0.1 epsilon), it clips the winding_t and creates +two points where there used to be only one. + + main() in main.c --> + BSPMain() in bsp.c --> + ProcessModels() in bsp.c --> + ProcessWorldModel() in bsp.c + FixTJunctions() in tjunction.c + FixBrokenSurface() in tjunction.c + +FixBrokenSurface() realizes that there are two points very close together +(in our case, since they were "snapped", the are coincident in fact). +Therefore it reports the surface to be broken. The drawable surface is +deleted as a result. + + +RELATED BUGS: +============= + +A lot of the math operations in the Radiant codebase cut corners like this +example demonstrates. There is a lot more code like this that can be +improved upon. In fact, it may make sense to use 64 bit floating points in +some important math operations (and then convert back to 32 bit for the return +values). Plans are to look at similar code and improve it. + +The following "issue" was discovered while doing research for this bug. +If FixBrokenSurface() sees two points very close together, it attempts to +partially fix the problem (the code is not complete) and then returns false, +which means that the surface is broken and should not be used. So in fact +it attempts to fix the problem partially but none of the fixes are used. +It seems that FixBrokenSurface() should be fixed to completely fix the case +where there are two close points, and should report the surface as fixed. +This might be a destabilizing change however, so if this is indeed fixed, it +may make sense to activate the fix only if a certain flag is set. + + +MORE NOTES: +=========== + +As stated above, the accuracy after revision 363 is: + + (67.000229 -1021.998657 0.000000) + (88.000175 -891.999146 -767.997437) + (133.999146 -1014.998779 0.000000) + +A further change was committed for a related problem in revision 377. After +this change: + + (66.99955750 -1022.00262451 0.00000000) + (87.99969482 -892.00170898 -768.00524902) + (133.99958801 -1015.00195312 0.00000000) + +The results look similar with respect to the amount of error present. diff --git a/regression_tests/q3map2/disappearing_sliver/maps/disappearing_sliver.map b/regression_tests/q3map2/disappearing_sliver/maps/disappearing_sliver.map new file mode 100644 index 0000000..8252886 --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver/maps/disappearing_sliver.map @@ -0,0 +1,73 @@ +{ +"classname" "worldspawn" +{ +( -704 -1152 0 ) ( 4480 -1152 0 ) ( -704 -1152 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 67 -1022 0 ) ( 67 -4096 0 ) ( 88 -892 -768 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 67 -1022 0 ) ( 88 -892 -768 ) ( 134 -1015 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 88 -892 -768 ) ( 88 -4096 -768 ) ( 134 -1015 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 134 -1015 0 ) ( 134 -4096 0 ) ( 67 -1022 0 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 584 -640 -768 ) ( 520 -640 -768 ) ( 520 -1160 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 528 -1160 320 ) ( 528 -640 320 ) ( 592 -640 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 528 -1152 0 ) ( 592 -1152 0 ) ( 592 -1152 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 520 -1160 0 ) ( 520 -640 0 ) ( 520 -640 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 584 -640 0 ) ( 520 -640 0 ) ( 520 -640 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -640 0 ) ( 512 -1160 0 ) ( 512 -1160 -768 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 512 -552 -768 ) ( -296 -552 -768 ) ( -296 -640 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -296 -640 320 ) ( -296 -552 320 ) ( 512 -552 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -296 -640 320 ) ( 512 -640 320 ) ( 512 -640 -768 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 512 -640 320 ) ( 512 -552 320 ) ( 512 -552 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 528 -632 320 ) ( -280 -632 320 ) ( -280 -632 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -536 320 ) ( -512 -624 320 ) ( -512 -624 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -512 -648 -768 ) ( -536 -648 -768 ) ( -536 -1152 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -536 -1152 320 ) ( -536 -648 320 ) ( -512 -648 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -536 -1152 320 ) ( -512 -1152 320 ) ( -512 -1152 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -1152 320 ) ( -512 -648 320 ) ( -512 -648 -768 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -512 -640 320 ) ( -536 -640 320 ) ( -536 -640 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -520 -648 320 ) ( -520 -1152 320 ) ( -520 -1152 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 512 -640 320 ) ( 24 -640 320 ) ( 24 -1152 320 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 48 -1152 328 ) ( 48 -640 328 ) ( 536 -640 328 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 24 -1152 440 ) ( 512 -1152 440 ) ( 512 -1152 328 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -1152 440 ) ( 512 -640 440 ) ( 512 -640 328 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -640 440 ) ( 24 -640 440 ) ( 24 -640 328 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -640 464 ) ( -512 -1152 464 ) ( -512 -1152 352 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -328 -896 -776 ) ( -512 -896 -776 ) ( -512 -1152 -776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -1152 -768 ) ( -512 -896 -768 ) ( -328 -896 -768 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -512 -1152 -768 ) ( -328 -1152 -768 ) ( -328 -1152 -776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -512 -896 -768 ) ( -512 -1152 -768 ) ( -512 -1152 -776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 512 -1152 -768 ) ( 512 -896 -768 ) ( 512 -896 -776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -368 -640 -824 ) ( -856 -640 -824 ) ( -856 -640 -960 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -512 -1168 320 ) ( -512 -1584 320 ) ( -512 -1584 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 1152 -1152 320 ) ( 176 -1152 320 ) ( 176 -1152 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 512 -1576 320 ) ( 512 -1160 320 ) ( 512 -1160 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 208 -1160 320 ) ( 1184 -1160 320 ) ( 1184 -1160 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 176 -1568 320 ) ( 176 -1152 320 ) ( 1152 -1152 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -1152 -768 ) ( -520 -1152 -768 ) ( -520 -1192 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"light" "3000" +"origin" "0 -768 -480" +"classname" "light" +} +{ +"angle" "270" +"origin" "0 -712 -640" +"classname" "info_player_deathmatch" +} +{ +"classname" "light" +"origin" "0 -832 160" +"light" "3000" +} diff --git a/regression_tests/q3map2/disappearing_sliver/winding_logging.patch b/regression_tests/q3map2/disappearing_sliver/winding_logging.patch new file mode 100644 index 0000000..a1babe9 --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver/winding_logging.patch @@ -0,0 +1,105 @@ +Index: tools/quake3/q3map2/brush.c +=================================================================== +--- tools/quake3/q3map2/brush.c (revision 391) ++++ tools/quake3/q3map2/brush.c (working copy) +@@ -421,10 +421,16 @@ + side_t *side; + plane_t *plane; + +- ++ static int brushord = -1; ++ brushord++; ++ ++ Sys_Printf("In CreateBrushWindings() for brush %i\n", brushord); ++ + /* walk the list of brush sides */ + for( i = 0; i < brush->numsides; i++ ) + { ++ Sys_Printf(" Handling side %i on the brush\n", i); ++ + /* get side and plane */ + side = &brush->sides[ i ]; + plane = &mapplanes[ side->planenum ]; +@@ -435,7 +441,13 @@ + #else + w = BaseWindingForPlane( plane->normal, plane->dist ); + #endif +- ++ ++ Sys_Printf(" Before clipping we have:\n"); ++ int z; ++ for (z = 0; z < w->numpoints; z++) { ++ Sys_Printf(" (%.8f %.8f %.8f)\n", w->p[z][0], w->p[z][1], w->p[z][2]); ++ } ++ + /* walk the list of brush sides */ + for( j = 0; j < brush->numsides && w != NULL; j++ ) + { +@@ -451,7 +463,20 @@ + #else + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); // CLIP_EPSILON ); + #endif +- ++ ++ Sys_Printf(" After clipping w/ side %i we have:\n", j); ++ if (w) ++ { ++ for (z = 0; z < w->numpoints; z++) ++ { ++ Sys_Printf(" (%.8f %.8f %.8f)\n", w->p[z][0], w->p[z][1], w->p[z][2]); ++ } ++ } ++ else ++ { ++ Sys_Printf(" winding is NULL\n"); ++ } ++ + /* ydnar: fix broken windings that would generate trifans */ + #if EXPERIMENTAL_HIGH_PRECISION_MATH_Q3MAP2_FIXES + FixWindingAccu(w); +Index: tools/quake3/q3map2/map.c +=================================================================== +--- tools/quake3/q3map2/map.c (revision 391) ++++ tools/quake3/q3map2/map.c (working copy) +@@ -803,7 +803,11 @@ + char shader[ MAX_QPATH ]; + int flags; + ++ static int brushord = -1; ++ brushord++; + ++ Sys_Printf("In ParseRawBrush() for brush %i\n", brushord); ++ + /* initial setup */ + buildBrush->numsides = 0; + buildBrush->detail = qfalse; +@@ -812,9 +816,12 @@ + if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) + MatchToken( "{" ); + ++ int sideord = -1; ++ + /* parse sides */ + while( 1 ) + { ++ sideord++; + if( !GetToken( qtrue ) ) + break; + if( !strcmp( token, "}" ) ) +@@ -917,7 +924,16 @@ + } + + /* find the plane number */ ++ Sys_Printf(" Side %i:\n", sideord); ++ Sys_Printf(" (%f %f %f)\n", planePoints[0][0], planePoints[0][1], planePoints[0][2]); ++ Sys_Printf(" (%f %f %f)\n", planePoints[1][0], planePoints[1][1], planePoints[1][2]); ++ Sys_Printf(" (%f %f %f)\n", planePoints[2][0], planePoints[2][1], planePoints[2][2]); + planenum = MapPlaneFromPoints( planePoints ); ++ Sys_Printf(" normal: (%.10f %.10f %.10f)\n", ++ mapplanes[planenum].normal[0], ++ mapplanes[planenum].normal[1], ++ mapplanes[planenum].normal[2]); ++ Sys_Printf(" dist: %.10f\n", mapplanes[planenum].dist); + side->planenum = planenum; + + /* bp: get the texture mapping for this texturedef / plane combination */ diff --git a/regression_tests/q3map2/disappearing_sliver2/README.txt b/regression_tests/q3map2/disappearing_sliver2/README.txt new file mode 100644 index 0000000..ffa12be --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver2/README.txt @@ -0,0 +1,93 @@ +DESCRIPTION OF PROBLEM: +======================= + +The example map, maps/disappearing_sliver2.map, contains an example of this +bug. The triangle sliver surface in the middle of the room is not rendered +in the final BSP. + +To trigger the bug, compile the map; you don't need -vis or -light. Only +-bsp (the first q3map2 stage) is necessary to trigger the bug. The only +entities in the map are a light and a info_player_deathmatch, so the map will +compile for any Q3 mod. + + +SOLUTION TO PROBLEM: +==================== + +It was discovered that BaseWindingForPlane() in polylib.c did some sloppy +mathematics with significant loss of precision. Those problems have been +addressed in commits to revisions 371 and 377. + + +POSSIBLE SIDE EFFECTS: +====================== + +Great care was taken to preserve the exact behavior of the original +BaseWindingForPlane() function except for the loss of precision. Therefore +no negative side effects should be seen. In fact performance may be +increased. + + +IN-DEPTH DISCUSSION: +==================== + +Turns out that the problem is very similar to the original disappearing_sliver +regression test. You should read that README.txt to familiarize yourself +with the situation. + +The thing we need to look at is side 0 of brush 0, if you applied +winding_logging.patch from disappearing_sliver regression test: + + In ParseRawBrush() for brush 0 + Side 0: + (6784.000000 16241.000000 -1722.000000) + (6144.000000 16083.000000 -1443.000000) + (6144.000000 16122.000000 -1424.000000) + +That is the exact plane defninition of our problem sliver, and in fact those +are also the correct points for the actual vertices of the triangle. + +Now the results of the winding for this surface after all the clipping takes +place: + + (6784.12500000 16241.02343750 -1722.06250000) + (6144.00000000 16082.99218750 -1443.00781250) + (6144.00000000 16122.00000000 -1424.00390625) + +As you can see, 6784.12500000 is more than epsilon distance (0.1) away from +the correct point. This is a big problem. + +After we apply the fix committed in revision 371, the result after clipping +is this: + + (6784.06250000 16241.01171875 -1722.03515625) + (6144.00000000 16082.99609375 -1443.00781250) + (6144.00000000 16122.00000000 -1424.00585938) + +As you can see, all points but one have an increase in accuracy. This is +still not accurate enough in my opinion, but is a step in the right direction. + +After the fix committed in revision 377, which is a further attempt to address +BaseWindingForPlane(), we get the following accuracy: + + (6784.00000000 16241.00000000 -1722.00000000) + (6144.00000000 16083.00000000 -1443.00000000) + (6144.00000000 16122.00000000 -1424.00000000) + +It's just a fluke for this particular case, but obviouly revision 377 looks +favorably upon this regression test, because there is zero percent error. + + +MORE NOTES: +=========== + +I attempted to improve upon revision 371 by streamlining the code in +BaseWindingForPlane() some more. Those attempts were committed as revision +375. After revision 375: + + (6784.09375000 16241.01757812 -1722.04687500) + (6144.00000000 16082.99414062 -1443.00390625) + (6144.00000000 16122.00000000 -1424.00097656) + +Revision 375 has since been reverted (undone) because of the loss in +accuracy. Revision 377 is a fix for those failed attempts. diff --git a/regression_tests/q3map2/disappearing_sliver2/maps/disappearing_sliver2.map b/regression_tests/q3map2/disappearing_sliver2/maps/disappearing_sliver2.map new file mode 100644 index 0000000..53e1871 --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver2/maps/disappearing_sliver2.map @@ -0,0 +1,69 @@ +{ +"message" "Icy Fantasy by Rambetter" +"classname" "worldspawn" +{ +( 6784 16241 -1722 ) ( 6144 16083 -1443 ) ( 6144 16122 -1424 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 6784 16241 -1722 ) ( 6784 16241 -2048 ) ( 6144 16083 -1443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 6144 16083 -1443 ) ( 6144 16083 -2048 ) ( 6144 16122 -1424 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 6144 16122 -1424 ) ( 6144 16122 -2048 ) ( 6784 16241 -1722 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 6144 16122 -2048 ) ( 6144 16083 -2048 ) ( 6784 16241 -2048 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 6136 16424 168 ) ( 6136 15928 168 ) ( 6136 15928 -3544 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6112 16896 160 ) ( 6000 16896 160 ) ( 6000 16896 -3552 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15936 160 ) ( 6144 16432 160 ) ( 6144 16432 -3552 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6032 15936 160 ) ( 6144 15936 160 ) ( 6144 15936 -3552 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6128 15936 -896 ) ( 6128 16432 -896 ) ( 6240 16432 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6160 16432 -2048 ) ( 6048 16432 -2048 ) ( 6048 15936 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6784 16672 -1296 ) ( 6784 16176 -1296 ) ( 6784 16176 -1776 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 16896 -1312 ) ( 6136 16896 -1312 ) ( 6136 16896 -1792 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6792 16160 -1296 ) ( 6792 16656 -1296 ) ( 6792 16656 -1776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6136 15936 -1280 ) ( 6144 15936 -1280 ) ( 6144 15936 -1760 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6136 16192 -896 ) ( 6136 16688 -896 ) ( 6144 16688 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16672 -2048 ) ( 6136 16672 -2048 ) ( 6136 16176 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6144 15936 -896 ) ( 6144 15880 -896 ) ( 6144 15880 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 15936 -896 ) ( 6144 15936 -896 ) ( 6144 15936 -2048 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6784 15880 -896 ) ( 6784 15936 -896 ) ( 6784 15936 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15928 -896 ) ( 6784 15928 -896 ) ( 6784 15928 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15880 -896 ) ( 6144 15936 -896 ) ( 6784 15936 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 15936 -2048 ) ( 6144 15936 -2048 ) ( 6144 15880 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6144 16960 -896 ) ( 6144 16896 -896 ) ( 6144 16896 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16904 -896 ) ( 6144 16904 -896 ) ( 6144 16904 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16896 -896 ) ( 6784 16960 -896 ) ( 6784 16960 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16896 -896 ) ( 6784 16896 -896 ) ( 6784 16896 -2048 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 16896 -896 ) ( 6144 16960 -896 ) ( 6784 16960 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16960 -2048 ) ( 6144 16960 -2048 ) ( 6144 16896 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6144 16896 -784 ) ( 6144 16536 -784 ) ( 6144 16536 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16896 -784 ) ( 6144 16896 -784 ) ( 6144 16896 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16536 -784 ) ( 6784 16896 -784 ) ( 6784 16896 -896 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15936 -744 ) ( 6784 15936 -744 ) ( 6784 15936 -856 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16552 -888 ) ( 6144 16912 -888 ) ( 6784 16912 -888 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16896 -896 ) ( 6144 16896 -896 ) ( 6144 16536 -896 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 6144 16544 -2048 ) ( 6144 15936 -2048 ) ( 6144 15936 -2376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16896 -2048 ) ( 6144 16896 -2048 ) ( 6144 16896 -2376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 15936 -2048 ) ( 6784 16544 -2048 ) ( 6784 16544 -2376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15936 -2048 ) ( 6784 15936 -2048 ) ( 6784 15936 -2376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15936 -2048 ) ( 6144 16544 -2048 ) ( 6784 16544 -2048 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6784 16584 -2056 ) ( 6144 16584 -2056 ) ( 6144 15976 -2056 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"classname" "info_player_deathmatch" +"origin" "6432 16168 -1472" +"angle" "180" +} +{ +"classname" "light" +"origin" "6440 16160 -1192" +"light" "1000" +} diff --git a/regression_tests/q3map2/disappearing_sliver3/NOTES.txt b/regression_tests/q3map2/disappearing_sliver3/NOTES.txt new file mode 100644 index 0000000..70bb752 --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver3/NOTES.txt @@ -0,0 +1,72 @@ +Random notes for Rambetter, don't expect to understand this: +============================================================ + +Brush 0 is the problem. + +Side 0 is the problem (under surf tri). +Side 1 is the +y 4-face. +Side 2 is the -x 4-face. +Side 3 is the -y 4-face. +side 4 is the +z tri. + +(6144, 16122) -> (6784, 16241) +x "climb" of side 1 is 6784 - 6144 = 640. +y "climb" of side 1 is 16241 - 16122 = 119. + +x/y "climb rate" of side 1 is 640 / 119 = 5.378151261. + +After clipping side 0 against side 1, we get +************ +**** (-262144, -33762.8125) -> (262144, 63722) +************ +The slope of that is (262144 + 262144) / (63722 + 33762.8125) = 5.378150571. + +(-262144, y) -> (6784, 16241) +So (6784 + 262144) / (16241 - y) = 640 / 119 +So y = 16241 - ((119 * (6784 + 262144)) / 640) = -33762.8 + +(6144, 16122) -> (262144, y) +So (262144 - 6144) / (y - 16122) = 640 / 119 +So y = 16122 + ((119 * (262144 - 6144)) / 640) = 63722 + +After clipping side 0 against side 1 should have +************ +**** (-262144, -33762.8) -> (262144, 63722) +************ + +Random notes for Rambetter, don't expect to understand this: +============================================================ + +Brush 0 is the problem. + +Side 0 is the problem (under surf tri). +Side 1 is the +y 4-face. +Side 2 is the -x 4-face. +Side 3 is the -y 4-face. +side 4 is the +z tri. + +(6144, 16122) -> (6784, 16241) +x "climb" of side 1 is 6784 - 6144 = 640. +y "climb" of side 1 is 16241 - 16122 = 119. + +x/y "climb rate" of side 1 is 640 / 119 = 5.378151261. + +After clipping side 0 against side 1, we get +************ +**** (-262144, -33762.8125) -> (262144, 63722) +************ +The slope of that is (262144 + 262144) / (63722 + 33762.8125) = 5.378150571. + +(-262144, y) -> (6784, 16241) +So (6784 + 262144) / (16241 - y) = 640 / 119 +So y = 16241 - ((119 * (6784 + 262144)) / 640) = -33762.8 + +(6144, 16122) -> (262144, y) +So (262144 - 6144) / (y - 16122) = 640 / 119 +So y = 16122 + ((119 * (262144 - 6144)) / 640) = 63722 + +After clipping side 0 against side 1 should have +************ +**** (-262144, -33762.8) -> (262144, 63722) +************ + diff --git a/regression_tests/q3map2/disappearing_sliver3/README.txt b/regression_tests/q3map2/disappearing_sliver3/README.txt new file mode 100644 index 0000000..839bd69 --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver3/README.txt @@ -0,0 +1,49 @@ +DESCRIPTION OF PROBLEM: +======================= + +The example map, maps/disappearing_sliver3.map, contains an example of this +bug. The triangle sliver surface in the middle of the room is not rendered +in the final BSP. + +To trigger the bug, compile the map; you don't need -vis or -light. Only +-bsp (the first q3map2 stage) is necessary to trigger the bug. The only +entities in the map are a light and a info_player_deathmatch, so the map will +compile for any Q3 mod. + + +SOLUTION TO PROBLEM: +==================== + +More work has been done to BaseWindingForPlane() to make it more accurate. +This function is in polylib.c. The changes to fix this regression test were +committed in revision 377; however, those changes are not "good enough". + + +IN-DEPTH DISCUSSION: +==================== + +This is the problem triangle: + + In ParseRawBrush() for brush 0 + Side 0: + (6144.000000 16122.000000 -2048.000000) + (6144.000000 16083.000000 -2048.000000) + (6784.000000 16241.000000 -2048.000000) + +Computed winding before fix: + + (6784.16406250 16241.04101562 -2048.00000000) + (6144.00000000 16122.00976562 -2048.00000000) + (6144.00000000 16083.00000000 -2048.00000000) + +Obviously the 6784.16406250 is beyond epsilon error. + +After revision 377: + + (6783.85937500 16240.96484375 -2048.00000000) + (6144.00000000 16121.99218750 -2048.00000000) + (6144.00000000 16083.00000000 -2048.00000000) + +Even though this fixes the regression test, the error in 6783.85937500 is +still greater than epsilon (but fortunately in the opposite direction). So +I don't consider this test case to be fixed quite yet. diff --git a/regression_tests/q3map2/disappearing_sliver3/maps/disappearing_sliver3.map b/regression_tests/q3map2/disappearing_sliver3/maps/disappearing_sliver3.map new file mode 100644 index 0000000..a56c649 --- /dev/null +++ b/regression_tests/q3map2/disappearing_sliver3/maps/disappearing_sliver3.map @@ -0,0 +1,68 @@ +{ +"classname" "worldspawn" +{ +( 6144 16122 -2048 ) ( 6144 16083 -2048 ) ( 6784 16241 -2048 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 134217728 0 0 +( 6144 16122 -1424 ) ( 6144 16122 -2048 ) ( 6784 16241 -1722 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 6144 16083 -1443 ) ( 6144 16083 -2048 ) ( 6144 16122 -1424 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 6784 16241 -1722 ) ( 6784 16241 -2048 ) ( 6144 16083 -1443 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +( 6784 16241 -1722 ) ( 6144 16083 -1443 ) ( 6144 16122 -1424 ) common/caulk 0 0 0 0.500000 0.500000 134217728 4 0 +} +{ +( 6160 16432 -2240 ) ( 6048 16432 -2240 ) ( 6048 15936 -2240 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6128 15936 -1376 ) ( 6128 16432 -1376 ) ( 6240 16432 -1376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6032 15936 160 ) ( 6144 15936 160 ) ( 6144 15936 -3552 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15936 -512 ) ( 6144 16432 -512 ) ( 6144 16432 -4224 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6112 16896 160 ) ( 6000 16896 160 ) ( 6000 16896 -3552 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6136 16424 168 ) ( 6136 15928 168 ) ( 6136 15928 -3544 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6144 16672 -2240 ) ( 6136 16672 -2240 ) ( 6136 16176 -2240 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6136 16192 -1376 ) ( 6136 16688 -1376 ) ( 6144 16688 -1376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6136 15936 -1280 ) ( 6144 15936 -1280 ) ( 6144 15936 -1760 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6792 16160 -1296 ) ( 6792 16656 -1296 ) ( 6792 16656 -1776 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16896 -1312 ) ( 6136 16896 -1312 ) ( 6136 16896 -1792 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16672 -1968 ) ( 6784 16176 -1968 ) ( 6784 16176 -2448 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 6784 15936 -2240 ) ( 6144 15936 -2240 ) ( 6144 15880 -2240 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15880 -1376 ) ( 6144 15936 -1376 ) ( 6784 15936 -1376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15928 -896 ) ( 6784 15928 -896 ) ( 6784 15928 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 15880 -1568 ) ( 6784 15936 -1568 ) ( 6784 15936 -2720 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 15936 -896 ) ( 6144 15936 -896 ) ( 6144 15936 -2048 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 15936 -1568 ) ( 6144 15880 -1568 ) ( 6144 15880 -2720 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6784 16960 -2240 ) ( 6144 16960 -2240 ) ( 6144 16896 -2240 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16896 -1376 ) ( 6144 16960 -1376 ) ( 6784 16960 -1376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16896 -896 ) ( 6784 16896 -896 ) ( 6784 16896 -2048 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6784 16896 -1568 ) ( 6784 16960 -1568 ) ( 6784 16960 -2720 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16904 -896 ) ( 6144 16904 -896 ) ( 6144 16904 -2048 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16960 -1568 ) ( 6144 16896 -1568 ) ( 6144 16896 -2720 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6784 16896 -1376 ) ( 6144 16896 -1376 ) ( 6144 16536 -1376 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 16552 -1368 ) ( 6144 16912 -1368 ) ( 6784 16912 -1368 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15936 -1224 ) ( 6784 15936 -1224 ) ( 6784 15936 -1336 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16536 -1264 ) ( 6784 16896 -1264 ) ( 6784 16896 -1376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16896 -1264 ) ( 6144 16896 -1264 ) ( 6144 16896 -1376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16896 -1264 ) ( 6144 16536 -1264 ) ( 6144 16536 -1376 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 6784 16584 -2248 ) ( 6144 16584 -2248 ) ( 6144 15976 -2248 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 15936 -2240 ) ( 6144 16544 -2240 ) ( 6784 16544 -2240 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 6144 15936 -2240 ) ( 6784 15936 -2240 ) ( 6784 15936 -2568 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 15936 -2240 ) ( 6784 16544 -2240 ) ( 6784 16544 -2568 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6784 16896 -2240 ) ( 6144 16896 -2240 ) ( 6144 16896 -2568 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 6144 16544 -2240 ) ( 6144 15936 -2240 ) ( 6144 15936 -2568 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"angle" "180" +"origin" "6432 16168 -2144" +"classname" "info_player_deathmatch" +} +{ +"light" "1000" +"origin" "6504 16160 -2152" +"classname" "light" +} diff --git a/regression_tests/q3map2/duplicate_plane/README.txt b/regression_tests/q3map2/duplicate_plane/README.txt new file mode 100644 index 0000000..8c2ef02 --- /dev/null +++ b/regression_tests/q3map2/duplicate_plane/README.txt @@ -0,0 +1,13 @@ +DESCRIPTION OF PROBLEM: +======================= + +The 4-sided brush in the middle of the room (brush 0) has a duplicate plane. +The last side (side 4) is a duplicate of the third side (side 2) with the +vertexes re-arranged. + +I wanted to make sure that ChopWindingInPlaceAccu() is doing the right thing +by setting a winding to NULL when all its points lie on the plane that the +winding is being chopped with. This seems to be the case. My concern was +that both windings for that duplicate plane will be NULL'ed out. + +Seems that some other code might be fixing duplicate planes. That's OK. diff --git a/regression_tests/q3map2/duplicate_plane/maps/duplicate_plane.map b/regression_tests/q3map2/duplicate_plane/maps/duplicate_plane.map new file mode 100644 index 0000000..d1913b4 --- /dev/null +++ b/regression_tests/q3map2/duplicate_plane/maps/duplicate_plane.map @@ -0,0 +1,67 @@ +{ +"classname" "worldspawn" +{ +( 136 128 64 ) ( -128 128 64 ) ( -128 -192 64 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 136 128 384 ) ( -128 128 384 ) ( -128 128 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 128 -192 0 ) ( -128 128 0 ) ( 128 -192 384 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -128 128 256 ) ( 128 128 64 ) ( -128 -192 256 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 128 -192 384 ) ( 128 -192 0 ) ( -128 128 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 256 256 -8 ) ( -256 256 -8 ) ( -256 -256 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -256 0 ) ( -256 256 0 ) ( 256 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -256 -256 8 ) ( 256 -256 8 ) ( 256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 8 ) ( 256 256 8 ) ( 256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 256 8 ) ( -256 256 8 ) ( -256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 8 ) ( -256 -256 8 ) ( -256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -256 256 0 ) ( -280 256 0 ) ( -280 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -280 -256 384 ) ( -280 256 384 ) ( -256 256 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -280 -256 384 ) ( -256 -256 384 ) ( -256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -256 384 ) ( -256 256 384 ) ( -256 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -256 256 384 ) ( -280 256 384 ) ( -280 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -264 256 392 ) ( -264 -256 392 ) ( -264 -256 8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 280 256 0 ) ( 256 256 0 ) ( 256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 384 ) ( 256 256 384 ) ( 280 256 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 384 ) ( 280 -256 384 ) ( 280 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 264 -256 384 ) ( 264 256 384 ) ( 264 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 280 256 384 ) ( 256 256 384 ) ( 256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 256 384 ) ( 256 -256 384 ) ( 256 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 256 256 384 ) ( -256 256 384 ) ( -256 -256 384 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -248 -256 392 ) ( -248 256 392 ) ( 264 256 392 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -256 424 ) ( 256 -256 424 ) ( 256 -256 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 424 ) ( 256 256 424 ) ( 256 256 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 256 424 ) ( -256 256 424 ) ( -256 256 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 424 ) ( -256 -256 424 ) ( -256 -256 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 256 296 0 ) ( -256 296 0 ) ( -256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 384 ) ( -256 296 384 ) ( 256 296 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 256 384 ) ( 256 256 384 ) ( 256 256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( 256 256 384 ) ( 256 296 384 ) ( 256 296 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 264 392 ) ( -256 264 392 ) ( -256 264 8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 296 384 ) ( -256 256 384 ) ( -256 256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( 256 -256 0 ) ( -256 -256 0 ) ( -256 -296 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -296 384 ) ( -256 -256 384 ) ( 256 -256 384 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -256 -264 392 ) ( 256 -264 392 ) ( 256 -264 8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -296 384 ) ( 256 -256 384 ) ( 256 -256 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 256 -256 384 ) ( -256 -256 384 ) ( -256 -256 0 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -256 -256 384 ) ( -256 -296 384 ) ( -256 -296 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"origin" "-8 16 256" +"classname" "info_player_deathmatch" +} +{ +"light" "1000" +"origin" "-32 -40 256" +"classname" "light" +} diff --git a/regression_tests/q3map2/model_clipping_45_degrees/README.txt b/regression_tests/q3map2/model_clipping_45_degrees/README.txt new file mode 100644 index 0000000..c3c2f88 --- /dev/null +++ b/regression_tests/q3map2/model_clipping_45_degrees/README.txt @@ -0,0 +1,7 @@ +DESCRIPTION OF PROBLEM: +======================= + +There are 3 model wedges in the map that are auto-clipped. The middle wedge +with the 45 degree slope is the problem. The slope face does not become solid; +therefore, there is a nonsolid "hole" on the wedge part. The other two wedges +are fine. 45 degrees seems to be a problem for model clipping. diff --git a/regression_tests/q3map2/model_clipping_45_degrees/maps/model_clipping_45_degrees.map b/regression_tests/q3map2/model_clipping_45_degrees/maps/model_clipping_45_degrees.map new file mode 100644 index 0000000..f358dba --- /dev/null +++ b/regression_tests/q3map2/model_clipping_45_degrees/maps/model_clipping_45_degrees.map @@ -0,0 +1,81 @@ +{ +"classname" "worldspawn" +{ +( 392 512 -8 ) ( 0 512 -8 ) ( 0 448 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 0 448 0 ) ( 0 512 0 ) ( 392 512 0 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 104 -192 0 ) ( 496 -192 0 ) ( 496 -192 -16 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 448 0 ) ( 384 512 0 ) ( 384 512 -16 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 400 640 0 ) ( 8 640 0 ) ( 8 640 -16 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 528 0 ) ( -128 464 0 ) ( -128 464 -16 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -128 688 320 ) ( -128 640 320 ) ( -128 640 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 648 320 ) ( -128 648 320 ) ( -128 648 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 640 320 ) ( 384 688 320 ) ( 384 688 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 640 320 ) ( 384 640 320 ) ( 384 640 0 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( -128 640 320 ) ( -128 688 320 ) ( 384 688 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 688 0 ) ( -128 688 0 ) ( -128 640 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -128 -184 328 ) ( -128 -240 328 ) ( -128 -240 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 -192 328 ) ( -128 -192 328 ) ( -128 -192 0 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 384 -240 328 ) ( 384 -184 328 ) ( 384 -184 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 -200 320 ) ( 384 -200 320 ) ( 384 -200 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 -240 320 ) ( -128 -184 320 ) ( 384 -184 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 -184 0 ) ( -128 -184 0 ) ( -128 -240 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -128 248 392 ) ( -128 -192 392 ) ( -128 -192 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 640 408 ) ( -128 640 408 ) ( -128 640 336 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 -192 392 ) ( 384 248 392 ) ( 384 248 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 -192 392 ) ( 384 -192 392 ) ( 384 -192 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 -176 328 ) ( -128 264 328 ) ( 384 264 328 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 248 320 ) ( -128 248 320 ) ( -128 -192 320 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( 384 640 320 ) ( 384 -192 320 ) ( 384 -192 0 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( 432 640 320 ) ( 384 640 320 ) ( 384 640 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 392 -192 320 ) ( 392 640 320 ) ( 392 640 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 -192 320 ) ( 432 -192 320 ) ( 432 -192 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 384 -192 320 ) ( 384 640 320 ) ( 432 640 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( 432 640 0 ) ( 384 640 0 ) ( 384 -192 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -136 640 328 ) ( -136 -192 328 ) ( -136 -192 8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 640 312 ) ( -224 640 312 ) ( -224 640 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 -192 312 ) ( -128 640 312 ) ( -128 640 -8 ) radiant_regression_tests/bigtile 0 0 0 0.500000 0.500000 0 0 0 +( -224 -192 312 ) ( -128 -192 312 ) ( -128 -192 -8 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -224 -192 320 ) ( -224 640 320 ) ( -128 640 320 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -128 640 0 ) ( -224 640 0 ) ( -224 -192 0 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"model" "models/mapobjects/wedges/wedge_shallow.ase" +"origin" "0 -96 0" +"classname" "misc_model" +} +{ +"model" "models/mapobjects/wedges/wedge_45.ase" +"origin" "0 160 0" +"classname" "misc_model" +} +{ +"model" "models/mapobjects/wedges/wedge_steep.ase" +"origin" "0 416 0" +"classname" "misc_model" +} +{ +"classname" "light" +"origin" "192 384 160" +"light" "800" +} +{ +"light" "800" +"origin" "192 64 160" +"classname" "light" +} +{ +"classname" "info_player_deathmatch" +"origin" "256 208 128" +"angle" "225" +} diff --git a/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_45.ase b/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_45.ase new file mode 100644 index 0000000..dc33883 --- /dev/null +++ b/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_45.ase @@ -0,0 +1,368 @@ +*3DSMAX_ASCIIEXPORT 200 +*COMMENT "Generated by Q3Map2 (ydnar) -convert -format ase" +*SCENE { + *SCENE_FILENAME "wedge_45.bsp" + *SCENE_FIRSTFRAME 0 + *SCENE_LASTFRAME 100 + *SCENE_FRAMESPEED 30 + *SCENE_TICKSPERFRAME 160 + *SCENE_BACKGROUND_STATIC 0.0000 0.0000 0.0000 + *SCENE_AMBIENT_STATIC 0.0000 0.0000 0.0000 +} +*MATERIAL_LIST { + *MATERIAL_COUNT 2 + *MATERIAL 0 { + *MATERIAL_NAME "textures/radiant_regression_tests/tile_model" + *MATERIAL_CLASS "Standard" + *MATERIAL_DIFFUSE 1.000000 1.000000 0.833333 + *MATERIAL_SHADING Phong + *MAP_DIFFUSE { + *MAP_NAME "textures/radiant_regression_tests/tile_model" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0 + *MAP_TYPE Screen + *BITMAP "..\textures\radiant_regression_tests\tile_model.tga" + *BITMAP_FILTER Pyramidal + } + } + *MATERIAL 1 { + *MATERIAL_NAME "noshader" + *MATERIAL_CLASS "Standard" + *MATERIAL_DIFFUSE 1.000000 1.000000 1.000000 + *MATERIAL_SHADING Phong + *MAP_DIFFUSE { + *MAP_NAME "noshader" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0 + *MAP_TYPE Screen + *BITMAP "..\noshader.tga" + *BITMAP_FILTER Pyramidal + } + } +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf0" + *NODE_TM { + *NODE_NAME "mat0model0surf0" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 128.000000 0.000000 0.000000 + *MESH_VERTEX 1 64.000000 0.000000 64.000000 + *MESH_VERTEX 2 128.000000 128.000000 0.000000 + *MESH_VERTEX 3 64.000000 128.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.707107 0.000000 0.707107 + *MESH_FACENORMAL 1 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 0 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 1 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 2 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 3 0.707107 0.000000 0.707107 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 1.000000 -1.000000 1.000000 + *MESH_TVERT 1 -1.000000 -1.000000 1.000000 + *MESH_TVERT 2 1.000000 3.000000 1.000000 + *MESH_TVERT 3 -1.000000 3.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf1" + *NODE_TM { + *NODE_NAME "mat0model0surf1" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 128.000000 64.000000 + *MESH_VERTEX 1 0.000000 128.000000 0.000000 + *MESH_VERTEX 2 64.000000 128.000000 64.000000 + *MESH_VERTEX 3 128.000000 128.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.707107 0.000000 0.707107 + *MESH_FACENORMAL 1 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 0 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 1 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 2 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 3 0.000000 1.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 2.000000 1.000000 + *MESH_TVERT 1 -2.000000 0.000000 1.000000 + *MESH_TVERT 2 0.000000 2.000000 1.000000 + *MESH_TVERT 3 2.000000 0.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf2" + *NODE_TM { + *NODE_NAME "mat0model0surf2" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 64.000000 0.000000 64.000000 + *MESH_VERTEX 1 0.000000 0.000000 64.000000 + *MESH_VERTEX 2 64.000000 128.000000 64.000000 + *MESH_VERTEX 3 0.000000 128.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.707107 0.000000 0.707107 + *MESH_FACENORMAL 1 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 0 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 1 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 2 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 3 0.000000 0.000000 1.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 1.000000 -1.000000 1.000000 + *MESH_TVERT 1 -1.000000 -1.000000 1.000000 + *MESH_TVERT 2 1.000000 3.000000 1.000000 + *MESH_TVERT 3 -1.000000 3.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf3" + *NODE_TM { + *NODE_NAME "mat0model0surf3" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 0.000000 64.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 0.000000 128.000000 64.000000 + *MESH_VERTEX 3 0.000000 128.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.707107 0.000000 0.707107 + *MESH_FACENORMAL 1 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 0 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 1 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 2 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 3 -1.000000 0.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 2.000000 1.000000 + *MESH_TVERT 1 -2.000000 0.000000 1.000000 + *MESH_TVERT 2 2.000000 2.000000 1.000000 + *MESH_TVERT 3 2.000000 0.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf4" + *NODE_TM { + *NODE_NAME "mat0model0surf4" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 128.000000 0.000000 0.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 64.000000 0.000000 64.000000 + *MESH_VERTEX 3 0.000000 0.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.707107 0.000000 0.707107 + *MESH_FACENORMAL 1 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 0 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 1 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 2 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 3 0.000000 -1.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 2.000000 0.000000 1.000000 + *MESH_TVERT 1 -2.000000 0.000000 1.000000 + *MESH_TVERT 2 0.000000 2.000000 1.000000 + *MESH_TVERT 3 -2.000000 2.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf5" + *NODE_TM { + *NODE_NAME "mat0model0surf5" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 128.000000 0.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 128.000000 128.000000 0.000000 + *MESH_VERTEX 3 128.000000 0.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.707107 0.000000 0.707107 + *MESH_FACENORMAL 1 0.707107 0.000000 0.707107 + *MESH_VERTEXNORMAL 0 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 1 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 2 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 3 0.000000 0.000000 -1.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 3.000000 1.000000 + *MESH_TVERT 1 -2.000000 -1.000000 1.000000 + *MESH_TVERT 2 2.000000 3.000000 1.000000 + *MESH_TVERT 3 2.000000 -1.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} diff --git a/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_shallow.ase b/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_shallow.ase new file mode 100644 index 0000000..ec5987f --- /dev/null +++ b/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_shallow.ase @@ -0,0 +1,368 @@ +*3DSMAX_ASCIIEXPORT 200 +*COMMENT "Generated by Q3Map2 (ydnar) -convert -format ase" +*SCENE { + *SCENE_FILENAME "wedge_shallow.bsp" + *SCENE_FIRSTFRAME 0 + *SCENE_LASTFRAME 100 + *SCENE_FRAMESPEED 30 + *SCENE_TICKSPERFRAME 160 + *SCENE_BACKGROUND_STATIC 0.0000 0.0000 0.0000 + *SCENE_AMBIENT_STATIC 0.0000 0.0000 0.0000 +} +*MATERIAL_LIST { + *MATERIAL_COUNT 2 + *MATERIAL 0 { + *MATERIAL_NAME "textures/radiant_regression_tests/tile_model" + *MATERIAL_CLASS "Standard" + *MATERIAL_DIFFUSE 1.000000 1.000000 0.833333 + *MATERIAL_SHADING Phong + *MAP_DIFFUSE { + *MAP_NAME "textures/radiant_regression_tests/tile_model" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0 + *MAP_TYPE Screen + *BITMAP "..\textures\radiant_regression_tests\tile_model.tga" + *BITMAP_FILTER Pyramidal + } + } + *MATERIAL 1 { + *MATERIAL_NAME "noshader" + *MATERIAL_CLASS "Standard" + *MATERIAL_DIFFUSE 1.000000 1.000000 1.000000 + *MATERIAL_SHADING Phong + *MAP_DIFFUSE { + *MAP_NAME "noshader" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0 + *MAP_TYPE Screen + *BITMAP "..\noshader.tga" + *BITMAP_FILTER Pyramidal + } + } +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf0" + *NODE_TM { + *NODE_NAME "mat0model0surf0" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 160.000000 0.000000 0.000000 + *MESH_VERTEX 1 64.000000 0.000000 64.000000 + *MESH_VERTEX 2 160.000000 128.000000 0.000000 + *MESH_VERTEX 3 64.000000 128.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.554700 0.000000 0.832050 + *MESH_FACENORMAL 1 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 0 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 1 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 2 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 3 0.554700 0.000000 0.832050 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 2.000000 -1.000000 1.000000 + *MESH_TVERT 1 -1.000000 -1.000000 1.000000 + *MESH_TVERT 2 2.000000 3.000000 1.000000 + *MESH_TVERT 3 -1.000000 3.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf1" + *NODE_TM { + *NODE_NAME "mat0model0surf1" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 128.000000 64.000000 + *MESH_VERTEX 1 0.000000 128.000000 0.000000 + *MESH_VERTEX 2 64.000000 128.000000 64.000000 + *MESH_VERTEX 3 160.000000 128.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.554700 0.000000 0.832050 + *MESH_FACENORMAL 1 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 0 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 1 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 2 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 3 0.000000 1.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 2.000000 1.000000 + *MESH_TVERT 1 -2.000000 0.000000 1.000000 + *MESH_TVERT 2 0.000000 2.000000 1.000000 + *MESH_TVERT 3 3.000000 0.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf2" + *NODE_TM { + *NODE_NAME "mat0model0surf2" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 64.000000 0.000000 64.000000 + *MESH_VERTEX 1 0.000000 0.000000 64.000000 + *MESH_VERTEX 2 64.000000 128.000000 64.000000 + *MESH_VERTEX 3 0.000000 128.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.554700 0.000000 0.832050 + *MESH_FACENORMAL 1 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 0 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 1 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 2 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 3 0.000000 0.000000 1.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 1.000000 -1.000000 1.000000 + *MESH_TVERT 1 -1.000000 -1.000000 1.000000 + *MESH_TVERT 2 1.000000 3.000000 1.000000 + *MESH_TVERT 3 -1.000000 3.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf3" + *NODE_TM { + *NODE_NAME "mat0model0surf3" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 0.000000 64.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 0.000000 128.000000 64.000000 + *MESH_VERTEX 3 0.000000 128.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.554700 0.000000 0.832050 + *MESH_FACENORMAL 1 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 0 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 1 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 2 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 3 -1.000000 0.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 2.000000 1.000000 + *MESH_TVERT 1 -2.000000 0.000000 1.000000 + *MESH_TVERT 2 2.000000 2.000000 1.000000 + *MESH_TVERT 3 2.000000 0.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf4" + *NODE_TM { + *NODE_NAME "mat0model0surf4" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 160.000000 0.000000 0.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 64.000000 0.000000 64.000000 + *MESH_VERTEX 3 0.000000 0.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.554700 0.000000 0.832050 + *MESH_FACENORMAL 1 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 0 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 1 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 2 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 3 0.000000 -1.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 3.000000 0.000000 1.000000 + *MESH_TVERT 1 -2.000000 0.000000 1.000000 + *MESH_TVERT 2 0.000000 2.000000 1.000000 + *MESH_TVERT 3 -2.000000 2.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf5" + *NODE_TM { + *NODE_NAME "mat0model0surf5" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 128.000000 0.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 160.000000 128.000000 0.000000 + *MESH_VERTEX 3 160.000000 0.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.554700 0.000000 0.832050 + *MESH_FACENORMAL 1 0.554700 0.000000 0.832050 + *MESH_VERTEXNORMAL 0 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 1 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 2 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 3 0.000000 0.000000 -1.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 3.000000 1.000000 + *MESH_TVERT 1 -2.000000 -1.000000 1.000000 + *MESH_TVERT 2 3.000000 3.000000 1.000000 + *MESH_TVERT 3 3.000000 -1.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} diff --git a/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_steep.ase b/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_steep.ase new file mode 100644 index 0000000..59689a3 --- /dev/null +++ b/regression_tests/q3map2/model_clipping_45_degrees/models/mapobjects/wedges/wedge_steep.ase @@ -0,0 +1,368 @@ +*3DSMAX_ASCIIEXPORT 200 +*COMMENT "Generated by Q3Map2 (ydnar) -convert -format ase" +*SCENE { + *SCENE_FILENAME "wedge_steep.bsp" + *SCENE_FIRSTFRAME 0 + *SCENE_LASTFRAME 100 + *SCENE_FRAMESPEED 30 + *SCENE_TICKSPERFRAME 160 + *SCENE_BACKGROUND_STATIC 0.0000 0.0000 0.0000 + *SCENE_AMBIENT_STATIC 0.0000 0.0000 0.0000 +} +*MATERIAL_LIST { + *MATERIAL_COUNT 2 + *MATERIAL 0 { + *MATERIAL_NAME "textures/radiant_regression_tests/tile_model" + *MATERIAL_CLASS "Standard" + *MATERIAL_DIFFUSE 1.000000 1.000000 0.833333 + *MATERIAL_SHADING Phong + *MAP_DIFFUSE { + *MAP_NAME "textures/radiant_regression_tests/tile_model" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0 + *MAP_TYPE Screen + *BITMAP "..\textures\radiant_regression_tests\tile_model.tga" + *BITMAP_FILTER Pyramidal + } + } + *MATERIAL 1 { + *MATERIAL_NAME "noshader" + *MATERIAL_CLASS "Standard" + *MATERIAL_DIFFUSE 1.000000 1.000000 1.000000 + *MATERIAL_SHADING Phong + *MAP_DIFFUSE { + *MAP_NAME "noshader" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0 + *MAP_TYPE Screen + *BITMAP "..\noshader.tga" + *BITMAP_FILTER Pyramidal + } + } +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf0" + *NODE_TM { + *NODE_NAME "mat0model0surf0" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 112.000000 0.000000 0.000000 + *MESH_VERTEX 1 64.000000 0.000000 64.000000 + *MESH_VERTEX 2 112.000000 128.000000 0.000000 + *MESH_VERTEX 3 64.000000 128.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.800000 0.000000 0.600000 + *MESH_FACENORMAL 1 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 0 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 1 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 2 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 3 0.800000 0.000000 0.600000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 0.000000 1.000000 + *MESH_TVERT 1 -2.000000 2.000000 1.000000 + *MESH_TVERT 2 2.000000 0.000000 1.000000 + *MESH_TVERT 3 2.000000 2.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf1" + *NODE_TM { + *NODE_NAME "mat0model0surf1" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 128.000000 64.000000 + *MESH_VERTEX 1 0.000000 128.000000 0.000000 + *MESH_VERTEX 2 64.000000 128.000000 64.000000 + *MESH_VERTEX 3 112.000000 128.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.800000 0.000000 0.600000 + *MESH_FACENORMAL 1 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 0 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 1 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 2 0.000000 1.000000 0.000000 + *MESH_VERTEXNORMAL 3 0.000000 1.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -1.000000 2.000000 1.000000 + *MESH_TVERT 1 -1.000000 0.000000 1.000000 + *MESH_TVERT 2 1.000000 2.000000 1.000000 + *MESH_TVERT 3 2.500000 0.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf2" + *NODE_TM { + *NODE_NAME "mat0model0surf2" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 64.000000 0.000000 64.000000 + *MESH_VERTEX 1 0.000000 0.000000 64.000000 + *MESH_VERTEX 2 64.000000 128.000000 64.000000 + *MESH_VERTEX 3 0.000000 128.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.800000 0.000000 0.600000 + *MESH_FACENORMAL 1 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 0 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 1 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 2 0.000000 0.000000 1.000000 + *MESH_VERTEXNORMAL 3 0.000000 0.000000 1.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 1.000000 -1.000000 1.000000 + *MESH_TVERT 1 -1.000000 -1.000000 1.000000 + *MESH_TVERT 2 1.000000 3.000000 1.000000 + *MESH_TVERT 3 -1.000000 3.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf3" + *NODE_TM { + *NODE_NAME "mat0model0surf3" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 0.000000 64.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 0.000000 128.000000 64.000000 + *MESH_VERTEX 3 0.000000 128.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.800000 0.000000 0.600000 + *MESH_FACENORMAL 1 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 0 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 1 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 2 -1.000000 0.000000 0.000000 + *MESH_VERTEXNORMAL 3 -1.000000 0.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -2.000000 2.000000 1.000000 + *MESH_TVERT 1 -2.000000 0.000000 1.000000 + *MESH_TVERT 2 2.000000 2.000000 1.000000 + *MESH_TVERT 3 2.000000 0.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf4" + *NODE_TM { + *NODE_NAME "mat0model0surf4" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 112.000000 0.000000 0.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 64.000000 0.000000 64.000000 + *MESH_VERTEX 3 0.000000 0.000000 64.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.800000 0.000000 0.600000 + *MESH_FACENORMAL 1 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 0 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 1 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 2 0.000000 -1.000000 0.000000 + *MESH_VERTEXNORMAL 3 0.000000 -1.000000 0.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 2.500000 0.000000 1.000000 + *MESH_TVERT 1 -1.000000 0.000000 1.000000 + *MESH_TVERT 2 1.000000 2.000000 1.000000 + *MESH_TVERT 3 -1.000000 2.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} +*GEOMOBJECT { + *NODE_NAME "mat0model0surf5" + *NODE_TM { + *NODE_NAME "mat0model0surf5" + *INHERIT_POS 0 0 0 + *INHERIT_ROT 0 0 0 + *INHERIT_SCL 0 0 0 + *TM_ROW0 1.0 0 0 + *TM_ROW1 0 1.0 0 + *TM_ROW2 0 0 1.0 + *TM_ROW3 0 0 0 + *TM_POS 0.000000 0.000000 0.000000 + } + *MESH { + *TIMEVALUE 0 + *MESH_NUMVERTEX 4 + *MESH_NUMFACES 2 + *COMMENT "SURFACETYPE MST_PLANAR" + *MESH_VERTEX_LIST { + *MESH_VERTEX 0 0.000000 128.000000 0.000000 + *MESH_VERTEX 1 0.000000 0.000000 0.000000 + *MESH_VERTEX 2 112.000000 128.000000 0.000000 + *MESH_VERTEX 3 112.000000 0.000000 0.000000 + } + *MESH_NORMALS { + *MESH_FACENORMAL 0 0.800000 0.000000 0.600000 + *MESH_FACENORMAL 1 0.800000 0.000000 0.600000 + *MESH_VERTEXNORMAL 0 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 1 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 2 0.000000 0.000000 -1.000000 + *MESH_VERTEXNORMAL 3 0.000000 0.000000 -1.000000 + } + *MESH_FACE_LIST { + *MESH_FACE 0 A: 0 B: 2 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + *MESH_FACE 1 A: 2 B: 3 C: 1 AB: 1 BC: 1 CA: 1 *MESH_SMOOTHING 0 *MESH_MTLID 0 + } + *MESH_NUMTVERTEX 4 + *MESH_TVERTLIST { + *MESH_TVERT 0 -1.000000 3.000000 1.000000 + *MESH_TVERT 1 -1.000000 -1.000000 1.000000 + *MESH_TVERT 2 2.500000 3.000000 1.000000 + *MESH_TVERT 3 2.500000 -1.000000 1.000000 + } + *MESH_NUMTVFACES 2 + *MESH_TFACELIST { + *MESH_TFACE 0 0 2 1 + *MESH_TFACE 1 2 3 1 + } + } + *PROP_MOTIONBLUR 0 + *PROP_CASTSHADOW 1 + *PROP_RECVSHADOW 1 + *MATERIAL_REF 0 +} diff --git a/regression_tests/q3map2/model_clipping_45_degrees/scripts/radiant_regression_tests.shader b/regression_tests/q3map2/model_clipping_45_degrees/scripts/radiant_regression_tests.shader new file mode 100644 index 0000000..a2c31ed --- /dev/null +++ b/regression_tests/q3map2/model_clipping_45_degrees/scripts/radiant_regression_tests.shader @@ -0,0 +1,12 @@ +textures/radiant_regression_tests/tile_model +{ + q3map_clipModel + q3map_forceMeta + { + map $lightmap + } + { + map textures/radiant_regression_tests/tile_model.tga + blendFunc filter + } +} diff --git a/regression_tests/q3map2/patch_seam/README.txt b/regression_tests/q3map2/patch_seam/README.txt new file mode 100644 index 0000000..b40bb60 --- /dev/null +++ b/regression_tests/q3map2/patch_seam/README.txt @@ -0,0 +1,8 @@ +DESCRIPTION OF PROBLEM: +======================= + +The seam where the two patches meet has holes. This is because the +triangulations of the two patch meshes don't match. + +It may or may not be possible to adjust the process of compiling patch mesh +to make good seams more probable. Investigate. diff --git a/regression_tests/q3map2/patch_seam/maps/patch_seam.map b/regression_tests/q3map2/patch_seam/maps/patch_seam.map new file mode 100644 index 0000000..a4dbeca --- /dev/null +++ b/regression_tests/q3map2/patch_seam/maps/patch_seam.map @@ -0,0 +1,85 @@ +{ +"classname" "worldspawn" +{ +patchDef2 +{ +radiant_regression_tests/green +( 3 3 0 0 0 ) +( +( ( -512 2368 -1088 -16 -74 ) ( -512 1984 -1088 -16 -62 ) ( -512 1856 -1056 -16 -58 ) ) +( ( -592 2368 -1088 -18.500000 -74 ) ( -592 1984 -1088 -18.500000 -62 ) ( -592 1856 -1064 -18.500000 -58 ) ) +( ( -704 2368 -1088 -22 -74 ) ( -704 1984 -1088 -22 -62 ) ( -704 1856 -1064 -22 -58 ) ) +) +} +} +{ +patchDef2 +{ +radiant_regression_tests/green +( 3 3 0 0 0 ) +( +( ( -704 1792 -1020 -22 -56 ) ( -704 1824 -1058 -22 -57 ) ( -704 1856 -1064 -22 -58 ) ) +( ( -616 1792 -1020 -19.250000 -56 ) ( -616 1824 -1058 -19.250000 -57 ) ( -592 1856 -1064 -18.500000 -58 ) ) +( ( -512 1792 -1008 -16 -56 ) ( -512 1824 -1048 -16 -57 ) ( -512 1856 -1056 -16 -58 ) ) +) +} +} +{ +( -448 2496 -1160 ) ( -768 2496 -1160 ) ( -768 1984 -1160 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 1856 -1152 ) ( -768 2368 -1152 ) ( -448 2368 -1152 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -768 1728 -1152 ) ( -448 1728 -1152 ) ( -448 1728 -1168 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 1856 -1152 ) ( -448 2368 -1152 ) ( -448 2368 -1168 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 2496 -1152 ) ( -768 2496 -1152 ) ( -768 2496 -1168 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 2368 -1152 ) ( -768 1856 -1152 ) ( -768 1856 -1168 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -768 2496 -1152 ) ( -824 2496 -1152 ) ( -824 1728 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -824 1728 -768 ) ( -824 2496 -768 ) ( -768 2496 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -824 1728 -768 ) ( -768 1728 -768 ) ( -768 1728 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 1728 -768 ) ( -768 2496 -768 ) ( -768 2496 -1152 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -768 2496 -768 ) ( -824 2496 -768 ) ( -824 2496 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -776 2496 -768 ) ( -776 1728 -768 ) ( -776 1728 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -320 2496 -1152 ) ( -448 2496 -1152 ) ( -448 1728 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 1728 -768 ) ( -448 2496 -768 ) ( -320 2496 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 1728 -768 ) ( -320 1728 -768 ) ( -320 1728 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -440 1728 -752 ) ( -440 2496 -752 ) ( -440 2496 -1136 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -320 2496 -768 ) ( -448 2496 -768 ) ( -448 2496 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 2496 -768 ) ( -448 1728 -768 ) ( -448 1728 -1152 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +} +{ +( -448 2496 -768 ) ( -768 2496 -768 ) ( -768 1728 -768 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -752 1728 -760 ) ( -752 2496 -760 ) ( -432 2496 -760 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 1728 -704 ) ( -448 1728 -704 ) ( -448 1728 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 1728 -704 ) ( -448 2496 -704 ) ( -448 2496 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 2496 -704 ) ( -768 2496 -704 ) ( -768 2496 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 2496 -704 ) ( -768 1728 -704 ) ( -768 1728 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -448 2552 -1152 ) ( -768 2552 -1152 ) ( -768 2496 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 2496 -768 ) ( -768 2552 -768 ) ( -448 2552 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 2496 -768 ) ( -448 2496 -768 ) ( -448 2496 -1160 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -448 2496 -768 ) ( -448 2552 -768 ) ( -448 2552 -1160 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 2504 -760 ) ( -768 2504 -760 ) ( -768 2504 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 2552 -768 ) ( -768 2496 -768 ) ( -768 2496 -1160 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +{ +( -448 1728 -1152 ) ( -768 1728 -1152 ) ( -768 1656 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 1656 -768 ) ( -768 1728 -768 ) ( -448 1728 -768 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -768 1720 -752 ) ( -448 1720 -752 ) ( -448 1720 -1136 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 1656 -768 ) ( -448 1728 -768 ) ( -448 1728 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +( -448 1728 -768 ) ( -768 1728 -768 ) ( -768 1728 -1152 ) radiant_regression_tests/tile 0 0 0 0.500000 0.500000 0 0 0 +( -768 1728 -768 ) ( -768 1656 -768 ) ( -768 1656 -1152 ) common/caulk 0 0 0 0.500000 0.500000 0 4 0 +} +} +{ +"angle" "270" +"origin" "-608 2048 -976" +"classname" "info_player_deathmatch" +} +{ +"light" "1000" +"origin" "-608 1944 -872" +"classname" "light" +} diff --git a/regression_tests/q3map2/patch_seam/textures/radiant_regression_tests/green.tga b/regression_tests/q3map2/patch_seam/textures/radiant_regression_tests/green.tga new file mode 100644 index 0000000000000000000000000000000000000000..8f6d3a49fd54a0c27e7eda57eb16f178a81cd03c GIT binary patch literal 300 scmZQz;9`IQ2L=ZQ35NX)|3@^2g+@qFsB4&Ku&2MDZiJhskE?QPnI>&#PXEl0_DmCYFnHhOeUW8L)@Ez5EXlIveUoKd^1fnY48|D87-NH3 zjDgr}AwUTG8o~|<0g{lAkhEFakZF^4Ch2rP?VK~uJojQNZWE5q{F~0{_4%at`|iEE z@B6*m^S;+QUGS{$`_B4*b!2B}2A96Y~%iV?$C(5z=)|WaV`rzoZ)_ z6{}HE-HYm)e$>_vps95|+B=5Oy=n`3`*xsz-8eQ3Phj)reb_#B0K0a*h-T<)`wxDKhaY@~kAM0(9)0#JeDTY#@a0#(#qWOiNBsHE z|AlWm$U8U=e47jc{~BW*qGNI(iLfFrJ_k~1F0^TtFk72YT(%0%sy>vv22oSL4)slg zXlfon=ZY=p?ioW*?`8}PPGZBx0~pWv4K z;a%Lm`vKm1`xCtT@Kb#F@z3$-(HHpqi!Uj|Zz#hbD8paAA;Jv)jejeHz`smcjR^7| z6p{&v#Ej@z8+B3+Lq-jCQ3;E+o^o`fu5mqTn}$)}GJ<8x$I!ZbJGy$tv1VW|21gEH zWDEHpKZ>1`XR+^vSMcJ|t2lb{I!>Rzi;I`<OI_g^#NYL`w(y4{{Ro({SY61`011k|3Mjk^9>oi@h^!l!rXZZ zEb&jn!i91al&*xq+J^F~0a&u?L_eskTL+`bCC&>9T2NBnMLkd#Z6oMdz7eZdZbfhJ z7;R}MHjM1S=+=YSvFj!5-hCYV51hfF7cb)EscSfM?mAw6_JZ93RrSmQRW^%OV1uS zU45wFC@AhgX-PY(s=CqKHiT8ZJLn5zqU{b2K8N9rd$48eA#uKE-zl6teS_n=$F}d@ zdx%$Wzl}HF`mtwCAb$395zeMDD77x6 z8S3G1uS0Ro2H5jDQCdF)i=_$mt;1;V9!EvxYPj4zVoYjnAI9qb2@fxW!v{RH@cb8F zx-8DmU3gW9B_e&}<{P;8#(N??#iK|+`s|mCOJCxvU;mc2^e6Fpo)_ZJxB72bL^kHl zS0Exf58+X{Q0f~H6lOt+q7qfj)a@)+<>a4?J(x9LT%S1is%c@=FPC?v_qfK1oyJ7u$dcCkk>3ezlwhCuId$O zfv+vwk9zi5nD3fA={YCNg+uthboq{l7UsebWccWlU*I!Q4_|rZUt?&*lBHVAo0o#8 zXD1;t+6tp>Ig*r4RMaufk6eJWaVr|u97Z)|D6L%wNA+5mGU{N@s`Wg--o4sG_tKWu zuHEUGkMBC}p${Lq0QR9pWxy9pWtI5wm$mYEB|tJ6?}bl@b%MU z>C!ZWN9Q0oJPQFKCi-p{D(W|*uzW2tY;9=j-h-Cz-Eh=zgh5+{oJ=>$7*~aTtEub9 zvaT_a?&#Qvfq@D7@d0tZZRct1+%dw zo6yoeiWcIeu2r1Z4Pec)&tolf^N^66M-O9k%Ry}2b`U#voxtvWFJsTat9aq18#s32 zhKQ3cU49)eUwn-|eMig>kM-~qul&a*6k_251?J63!R)7{2nn|#Dms_4)IzRuL2YP4 zs3eE6DUZI93t4g*aYZdG<~HQycfj4g6Sd3z=NP!9h9J+Y7#i+oh@^-pI^YCu+QH!~!jdo+D-DE<~_*2$0i4@va z0P|W{gbk4qcElwXK_;(2I`Kt8)i6pb`;lL{9@myS{&r?27Sr8~HG z<1MfJ=a=KdZ zaqZ@PT)*j^f2phpix(>3@2h7VH9#tNATlZk0by2zF%C&0v&Hx-JV=Oh5{V5<7N#L4 zwvc$PQGC9zK0O+je;xN5T1GwRTUx))&(qfmdJqm_e(66?KT~ z(;q5jDf04{d9H10-~2V*@|geWYu9f&j7?*YafQI+e)_?|7hhp)J%?S-9b{}3?CH|< zzC4kCm9`Q-K1$Xt`3MR%BO*4JSk8om6kz`RWGq^!^zacUEkuC72{QU`He;!9 zOz)@gU0B)!ljSk45Y7qyAgmXaK9+UnwT(^v!X@JQ;Z1vqwa;Pi^8)`zScejKAHFdC z+fU@*Ufj!=UypF&IDz+=gnY(OGxV9QV(t?3BZ;($&zboV9F&0~$Fnn+5GT!Nolplu zcBf}u;MZcUgVk2&SrZ6n)e6?seSftkOg4<{X6^7IWjTxICSLN&zs0@+3DP3gT(z|6 zT-K2pqAud3#gxg$*p$iozL+xDr+>@yZO!X~mic++o*KKpt2YE4+*FfxhfCXZoq&xz@CPuzcpeKq8YYSvyV`1u;ShsDIsRj}l( zMl5|Gk-Vp>-H2cgi%%-0u81X!^~fvfBPQLBO7}4AvHk25T-%m;2{ z}HI0;*Eh5T>;7|+FOzm8&M@D7?qG(rwPNE{S5T8&W+TIf4*_eb}7&4ov7dK_e zW4y9Kr71&Nnga&*1I%U@>^XHPW*zFP=_URnW+LVuq7M4{$1ya_zRd7$ul(l}7NR(( z2zF&5vbDrK3B^#T9K@dm2&FGXL}k-I@)?8V2nk6;BG-ldV5Y5Dv)gIYt+W-|8uLN6 zeHnRf7GpsQb(bP9Ms&1|_|-xCGa!*Nq^CJ4Lpk)ia#*cyQHHXzPS&EU*bCUiT6&zg zX)Ce!K4Ol?c$_+T;{Ip5YN4~3p-ogG@|j>reKbgwmZP|O1O?9ZD0Yt`uVM&VT^%C1 z$C5A;>#!B5t{s8N)QCdT`S~l6W$l1Q>n6`m+D$c*6tp)>JEEepV79d*BsiV^WJZ$I zPMPaqBKEd2E)^8A58&!WZ9_kri8uP!?ZbN34MU@c$p5(4{ZF-(KxVQcS*?aTJ{dWx z9O#HM4XJK~g&L8PT0vbDi}oMRSd_v27Vul9r4xEhC0bd_v=RS1?2mE7bU^eY1`H&Y z5)Mn&GO@QL&~b6u#IG(WQ;T8HSJD^ig=NpNa?K=q*6ctZ>!1FC=e+W-$#z4V=Y%%D z1Zi1WfI^wzhdPUul#2)SDGtUAjMRG z1g#zsvLsmaCS)ruNDj_|PSuQ1iaEJkOnY)EUS8A=^}$Yn0D zGu}`0C-5uuH5M%Vn*V@+bkF%rTPq~tw9VvP`pvgsz?LBhJ*B#awO;q%s;oj1YsC=O z?!G~(2#r-DG){(Sg#xLm3Zz6QL&=_K#C$d6>`6q@7oy?|L`C2hl&cpLIl^SN;WGRT2|R<|z@* z9Oe_CL}08MQe7tEH5zE>*ZQP1#LiEIF0l+@tS=?JCxpFTCF86qXB8A_b?mv*SLhGI z-Usk^g2T+1Gh52uo=#kwJ68rlhG`q0{?_m}s#(KL&xP+Qq5kRFiAu($sr4tuPr1Jq z>Eu5>x)?D&TKGIIWxcCMsMLUc!gpj~=1O{?nBqkJb(AY6fyBXkkmbFBySN=1?O;86IpjfPdF2DkX zPdem&W=O+`r$hBfP}-1e%wcTJCI-twrcw`ehywDUOk^2qVKfpuu+E83;5(=Y6Jq1- zjP*wLvr}0+$XNdqmxLLyXpw?-kQzaOI^v`v%$uFSzMl&I92V+N%e=E4|kf!;rh^8%!W+aV1mb|8+CY73F1=Ul6UN~?h(F%>Gm zB;;$VVI+1_Gsh`dm!+ieKB0d^{Rz1)jJev6crKjvoDb!hBifS+(v(sxp#AvJ&jSK< ztTkHL^RA#AZOF>+LS5T7Sncc)P#@m-&tg6_$Jmj+33#}#4$%CFh^+Pk|FbpK?-Z;#2_1D*mv}2A2mEQ13|*OoIv^zX`e-EV!0yv zy$MU{Ye543%V>tbzXm>w72@yD`yQr86s1Zh)qt0Dlr*~ASpD=b5Q}ASjv8p z(7%Wg@)z@UXs8K`7PB|Jfcs3|eVGIN{B%$oTUkRZXj?k&1$`lyvT_LTucG)aE;@=b z>Z`?mx3~Q_@;)v5Rx0|IL753zR4U|=dW16$X_vCjolkoY%7Kh~9KrWwN$Py~N2DMm zPKt=c1SsQ^sE26Un+=H}>?84a!nnu2tntP7Y1|hd$`wjG4G7S{k9H))tN=guC6}g( z?+gXsoG3rKJ{IZj^O;M8T5BZe~)Xw;(rW%cWPa@=bxwO z?~wkNr@k{Qli$DPH9vUj@o#>g^B>N*{zp@^@LNw$(F)SfOkF>V_s)IlpF|#hLVDg5 z{fPAZ8NU-aMGMch&RFMkRyv2k+w}MMXFdLtR5#Q)CC;{*vgR_0xw*mZbhNfe>{($+RfH@4S`_lQ^^Tt(nZ4KXOXgK~lGo z{%C(#;D5~m3WAX0zkvMuAMHP5fp^)`N=ZvU)VPa%l$n!5l#ab^l#xT%C|#S*(Mp;+ z!D3m9cP#y`dEPPXKh~unDJUrYJe#tLiE4j;*5c z>_N8Hm$Q2NT9TH{!6Q8Ik-n|<0!cyHSc$TQ`b5%-GD(^{hNEj;*}f%+$}Q6=sa?M%AH0o_=xv<)eq!UtGRf-!gr@9dYZm$=Pkisx5BZJ(R*%AD`gConzED zZXqjgJPA2zEoIx*zF1kB+cItTWco*pe5uvRvQ+5M_IK3d)&8osQST)i9eTHCLIm57 zh$9y(sX4R}wHJO}c znXKMW%+(9^Eg5qY@C_S*LqE3%3d$-@avc;DUd6DtzV+{__1ZVpHQdK!W&PE2W;}VB z4jf$9o4UnTTwZC+jzjVImP+`pl$f-^lgsxSSh719w=@a=6nCN)EyQkE5F58uux(c* zo63r~d$oZ*wHruIPr%)OC;=hC*!JoBXW>si|1RNQ-LIZUkHYO{u zL11&10#B=K*>ZLQUd0jvH!3n`yD>Mm+L6AgJzl8;88~S+20jyL(<2bOfLOYYOr-ay z$y8QX(s+0u&Vxg-@baO5h~LYI=z)w3cY5wS$RyIvPT#J5dyV$3xrhz<)t)Gdy%PKN ztNvE^Sr1E?{w#dyIy!}iv~0~bzfuF zxbDM+TPs<;bt*+0#!$X9k|pcHaTj*h>^2NFyPLLrN76#R^;A_YHG@A`ub}6*4EnA9v-G1{{9{P{Rf%( z$7T#TvG#BVTTf=ZynK5mFQ0_+mpeW@zc!q2uY~dR-dHYQn85Lqlh{?0#IBtQR9B@? zuq2z}C9^48mBHfKxx|joW6SFK)a@EadzT>uMGfWj;qeqLoKJ6;Vw4p(;?cX5Nl`25 z?@^4JS}7J*MNF8mkRP8!F*dw&+*_`1k1A@)>N8TD(~lkVKX&C}(3RU)N8f&Meaz!~ z*JHo`pdtBhXO1VgtSHE$aMA3S%X8TE*(+3rS8`ilxnJ%sZB#rBjMi zmomy$+1^Zg`U~mJRi*V z8ZXW_1yNI$iF&(I`MoWTd!|yD)AJ+2+wLuQ`7tZgIcrx{Istw~3>>hGer_wUv09C~ z=4uA?Tfv%zdGv5B5p$FwDX&9PlFzr9v7w8(cP@gbSH|PqZLR3rB6Q2+`l(2YSM{Z~ zvOhoEnt--hDe6XLoZp|w@tt0H`&yu+sZZ^S!DLM<6!%piscm6ebUqIoN6@9KK8or( z$hw)*u+@#d8)k{?1JP9j1VMEbO}BB)Nr?EGy{A(p<(Wbo9}3@=LFN= zeGR>OSMgD4I4;f>Xqa}!&^3?|0YRKhnS_zuW;8mMqhYZH?M{{K9-mG_xG!!pSCl$A zGc`Jz!f+=x&FoL@;t>RU3)@Hv*9CvgRyvpq`A|-j9s5^^y)vyqtBc@hT20FE0;(qt zA#O?-7mA|k>>zyEr0Uh0nx+-x`sHC`QOSv^Nz5E-iIIgwVwNAxYZg=Ela8`x9reMP zWca!uwK1pLz=?Dpoq&h0cy0ry(!;m{b@f_wHEU2+-+^Y^?W}cO!JQGwWV|<$UT(wj zcecXbKti*f)FQYG{?aJ{jaK~SVvqi|dLLd+iln<$IV!qaQPFST-XUIWS~HwyQFHOo+(k%-y__8ujJ<^( zn$}iW`^C^@P%OqS!#Ff}8kyELg3n0|q{A3WhtL%BsYu5NY`>pVeakrKmds@5KA737 z37=N}TJU@r-lzB-9a8Jj$*91A4REw>aOd_>laC0(hTkg7N95k z^u!w40*2CYPC0I&$RPj&r(Ogk<#B3aF8i!EqA#80o@+75t`2mzmncYU$AbB7Nt~;L zORNKtUXk4IwE{nlQz%I1Fcx~42$+ibRi#FuQxjQ62l+-;Kt*?d^bCz}3eFJ%j;*-M z#h>3>(>pt#Iq9;7j#6WbzI0k}{D2W^O70%=5H$=?WK2O1N!4_M74Dx(xpX_kTFGQ8$?WnQaOo zYG+>POE+K5V<9zjrrm6&IQp`#cPvdh`55RlBo7j;ttB`K>_mSsm`%qnJ?VF#9Vab(c`GZZE+rNZ0_ zN1F~LCb@FFCV(9qtSMctz>;}N%**7&^7J!%Zgq@s4{H-rID(Z{Ttf9s(E}=+g5II=uX(hLw~n;upOE#_6XyN&lkGyns;SPiXKbz+A}iQgyG)WB&utZ+_@hG=g(8Y*R1*O z6N;XF%HppdG4C&bV!UT8?vl<6RYj$L=0^q^lJ>13eaEu@%R6j;aE%?0Z&LPQGlfSl zV6Qz;#1Z+?xb^i#9{zZhkG{Xc zhtIC^m9S?nZI0z z0PhI;MI9jJ(ql%mhKoTcrs~r8RehcWmEGdDw{8I z;OpB=&sZg5Y)=tmweJf4wyj)8F20VX3Q~7pwdFre>C{Zn!2JYy?q!nm1*U4AK}XXG zExVqKGvCiblZQme-e;o20n$31qfp~ROtkx9)Xkfiv~nri`x0!saP^Adv=G~k!hNP_w26t_Q0X>cG5W(1<_?2MXeAWmAvcn&x~+{q7#*?W`tgUv$c z2lO9ZiLpiy>PmXw2)>@b;Q#aK5Y3Qd=Q@4G0;&FGvQ)!Gvu9*?SdM~$F$Qjdg!Ns; zBfIA;67S3uX&YBMJ!7@iCKPlnuqSgD;Z}cXBhJ8AoFoYL+s(3`wW!&@>uGouStC z5;HpO;$g@``dWIUY~n~qw^Wg*Ww9`SD-|_$Y+SyS8*}|xGSQLTK|XwL{W;szACWE9 zGh6W>1sWGQXYnIHc(gzOyyydmO}}QM-C-hSdkJ-^BcSgNCYv`iQT+xT)JCJOtp2T{ zqJr-)XtzGKjc^(BWnC!zF}XlJ5<_#z#PLvWa`$kNdFL3 z=9f6rXzvQ{Y3)UL!#!jh^s39bg&q?KiZ-;kqZrx=ssVF{cXD4IIltg}b+)Ppa z8VtIHV&W2uY0qd;$4(OYb^v-^hvFWZiH*+`+Bl9vT{fP!uG5hm;+QykFnhB?SrTMN zc7iTBrP0h4IrgX#Axw0PrNUj*XG5B}A9RbSVW0AC*q3}e`YB%w{g{t^?$PLSgrp8h zShTT}^Oon(R|Vv`kXmc9zmVU?)@6ovj>v|LXTgGe`2H!&mTjP&^+ z0`lDdh=82Cp}_yAxyx%G-+&05`j2mM@*2h2Qzs~|-ixjGG$PZAaToRLxLK9>CvBoj zu+YUP6UiwayOGiCUYS7o=w76HTj3wvm$>vCCTFf>(u^Y3ZaIK#U@TqwjYY>w)M@QZ zTO>pAJSi#5{p6&;R)JLla^8Ieep!2ouHV$6{oe{zEP!Ejkd*U3JPR`02s&}7c%B0%Up29FZ)WmZc}YChMl+-6l- zEpeH}?5aOSSyeqBoWDu&rUUG4xX$%UH`qCEGTj~QQMMmQk73jCi_8{&ACHZDC@ww` zbQXEBYDbyi-U$U|RXO+BzlqInAJqCei4?c}wXLZ&Bjd7OEL&UK(s=qRr%qjDbM--L z>W;8=R|7M0%9%R9hIx6b*|a=|IRzCY&MzZee8UQyIGgcP7BDO_nLZ<8T9hoh|0HP| z$a%|sU0x&p-mRBd;H&`pA{QDRpUKAEr@8asD+<=t;vPJM+*LI!D&9@*n%$f@)6CWD zA93OGJ?cb#v$o;@!{X;*&|@G9#&Ta*k#oN&@Sec$-QL7i1r@O=GsI%@JPI8%8(e%x zF?Qw>5(_F>we=_s=kM~t^#@efpJ&C^L!=d!GdMaO6PbuL?S#FRUir%R;(obbstCy2 zuWG8--M`!ac4Fqq0!;#6381Q`hKh)TN_u7}8HkvuWh%bOiFhcky}@_lC?B6Fpe^vW zwf57uegDm)Oe{8DV3~ltzL0oI@Ge^}smwScbptgHNY*A+ji%iXfOfcyvcx3Z#;<*xjNjd0mF{qk$7Z)ADM z(^L?jP{r|kF-BM6^_=SBT(8x5!#A>guJ&)_{z5i-GhUuAm@25eTKBGyJG_w}3)!sI zJYC+%@^u4S^@~$!kbs=q+oQVl`cD>@I4wpNGh@nx c #264777", +", c #354974", +"< c #384B77", +"1 c #3A517B", +"2 c #395276", +"3 c #43546A", +"4 c #42567A", +"5 c #445A7C", +"6 c #485A7C", +"7 c #415573", +"8 c #525C79", +"9 c #424F7D", +"0 c #52637E", +"q c #636565", +"w c #63667D", +"e c #977C7E", +"r c #AE7E7C", +"t c #3D5386", +"y c #3B5587", +"u c #3D548C", +"i c #425483", +"p c #445A83", +"a c #4B5B82", +"s c #445B8C", +"d c #4C5D8B", +"f c #41578A", +"g c #435B93", +"h c #475E9A", +"j c #515C80", +"k c #496187", +"l c #54638E", +"z c #5C6A8C", +"x c #596587", +"c c #4D6293", +"v c #4C6599", +"b c #546592", +"n c #536997", +"m c #5C6C95", +"M c #546A9A", +"N c #5A6C9D", +"B c #56679B", +"V c #5E729D", +"C c #626885", +"Z c #677286", +"A c #767689", +"S c #627391", +"D c #6B759B", +"F c #6F7C9A", +"G c #687597", +"H c #767A9A", +"J c #696991", +"K c #4963A1", +"L c #5C6DA3", +"P c #566AA6", +"I c #5E71A3", +"U c #5C72A8", +"Y c #556AB8", +"T c #516BB9", +"R c #6D7AA2", +"E c #6777A6", +"W c #737EA2", +"Q c #757CA6", +"! c #606FB2", +"~ c #6C7CB4", +"^ c #6878B4", +"/ c #767CB5", +"( c #626CAB", +") c #6377C5", +"_ c #727BC1", +"` c #89788B", +"' c #937F87", +"] c #A17E8F", +"[ c #857CAD", +"{ c #7681A3", +"} c #7583AB", +"| c #7C86AD", +" . c #7D86A8", +".. c #7C87B3", +"X. c #7B8AB4", +"o. c #7B86BC", +"O. c #7D8CBC", +"+. c #7582B7", +"@. c #6D80A2", +"#. c #7987C3", +"$. c #7E8CC2", +"%. c #7483C7", +"&. c #7B8AD5", +"*. c #7F93D0", +"=. c #7894E7", +"-. c #6D8CE7", +";. c #868499", +":. c #98839B", +">. c #8C8B92", +",. c #A88699", +"<. c #B89899", +"1. c #A88588", +"2. c #838BAB", +"3. c #8589AA", +"4. c #9489AA", +"5. c #8B93AC", +"6. c #8790AC", +"7. c #9393A5", +"8. c #838CB3", +"9. c #848EBC", +"0. c #8B8EB5", +"q. c #918AB3", +"w. c #8C95B4", +"e. c #8592BA", +"r. c #8B92BB", +"t. c #8E99BE", +"y. c #8790B3", +"u. c #9395BC", +"i. c #959BBD", +"p. c #989BB5", +"a. c #A897AB", +"s. c #A79AB5", +"d. c #B799BC", +"f. c #AC8DAD", +"g. c #9DA3BD", +"h. c #A2A2BE", +"j. c #ABADBD", +"k. c #ABA7BA", +"l. c #BCA5BD", +"z. c #C4909F", +"x. c #D19DB1", +"c. c #E19DA2", +"v. c #CBAEBC", +"b. c #838EC2", +"n. c #8C94C3", +"m. c #8B94CC", +"M. c #8796C8", +"N. c #949CC3", +"B. c #949BCB", +"V. c #9799CB", +"C. c #808FD1", +"Z. c #8595DC", +"A. c #8897D7", +"S. c #969BD8", +"D. c #A49AC5", +"F. c #9BA1C2", +"G. c #9AA3CC", +"H. c #9EAAD6", +"J. c #92A1DA", +"K. c #9BA6DB", +"L. c #99A5DA", +"P. c #8FA3DD", +"I. c #A1A3C3", +"U. c #A5ABCB", +"Y. c #A8ACC6", +"T. c #B8AAC6", +"R. c #AFB1C6", +"E. c #B8B8CB", +"W. c #A3ACD4", +"Q. c #A9ACD2", +"!. c #A5A9D8", +"~. c #ACB3D5", +"^. c #AEB5DC", +"/. c #A5B2DC", +"(. c #B4BADD", +"). c #B8BBD6", +"_. c #B9ACDD", +"`. c #859BE3", +"'. c #8A9DE9", +"]. c #8EA3E3", +"[. c #8BA4EB", +"{. c #98A8E8", +"}. c #99ADF1", +"|. c #96B6F8", +" X c #A6ACE4", +".X c #B7AAE5", +"XX c #ACB5E3", +"oX c #ADB9E4", +"OX c #A4B3EC", +"+X c #A8B6E8", +"@X c #B3BBE5", +"#X c #B3BDEB", +"$X c #B6BAE3", +"%X c #A4B6F1", +"&X c #ADBCF3", +"*X c #A7B8F4", +"=X c #B6B8F2", +"-X c #A3AAF5", +";X c #C7A5C9", +":X c #D7ABCA", +">X c #C9A8D3", +",X c #D8BCDF", +"X:XX;Xd.4.A w C w h.{ { q.:.f.a B B g g h b.B.P ", +" =X>Xf.[ H H D D 8.z w.g.s.p.6.C f i d N d s L !.^ M ", +" }.S./ / n.N.B.i.2.@.h.R.g.7.T.l 6 D 0.Q p.w.(.W U P M 8 ", +" =._ 8.b.n.Q...9X9XZ E.rXj.6Xk...i.r...I. .3.iXB.9.U U x ", +" =.S.&.) n.kXqXU.i.6XF.h.7X6X1X5.m 6XY.h.H 1 : ~ Q B.o.m v * ", +" [.S.! P U R ~.Y.w.~.Y.6XE.E.6X1XH.i.J o i 6 N M p b m.n.I 6 ", +" A.%.M.h ^ E p g.R.i.R.jXE.6X .;. .9.Y.d d & : 1 X.R R L.X.k ", +"%X&.U.e.( } Q.L E.R.g.h.jXjXj.} p S A Y.0.| = f < N S 1 N.B.s # ", +"[.Z.*.qXU.t.{ b ,.;.s.k.E.7.Z l V 2 w & W N l < : f 1 k M D I 3 ", +"-.) P.W.| L g #.0.e 1.<.1.q A 0 6 & q . X O f B ; 6 y c y y n 3 ", +"-.) o.b.W.N.~ $X(.a.r z.a.7.0 V 2 & 2 2 , 9 a F a l y u N c y 2 ", +"-.T ~ ~ v u + + i 4.l.<.l.` x W 4 , - O O o ; 6 r.} I > L : c & ", +"=.Y Y ^ M.(.3.I t i a.7X8X5.} F a < p o = $ % X l i.V = t f c * ", +"'.A.S.U P Q.dX9Xi.W @.8XtXzXrXtXrXE.| , 0 @.G F o 2 t.= c : k 2 ", +"}.%.#X^ / U.9.o.h t X.S w.wXxXzXxXzXrXw.d F D 6.x 4 8.m : s N @ ", +" +XdX$.N.X.w.h.8 & r.B.i.S g.lXxXxX2Xe.z 5.W.G.F V E H.> y 1 ", +" {.gXb.G.i.1XrXh.a x y.kXrXjXxXxXqX(.{ 4 y.~.2Xy.F.X.K.I c & ", +" -.oXeXr.O.r.~.tX^.h.eXtXzXgX6Xw.} | S F 5.e.aX(.2XoX{.$.k @ ", +" +XyXXXb.G.N.(.dX~.@XqXfXwX@XW.G.t.H 5.0 8.{ 1 e.+XO.m 3 ", +" ].2X*X$.uXH.qXlXXXXXN.uX0XXXe.F.} 5.t.N.0 7 $ } +.k 6 # ", +" [.C.oX2XpXb.uX3X@X!.qX@X!.@XW.^.W.r.X.E O.J.*.B s 2 ", +" =.*.b.oXB.b.oXdXlXdX/.dXhXpXoX^.e.!.K.{.A.M.+.n ", +" ].%X2XyXyXdXaXaXfXlXdXhXdXgXsX/./.#X XM.#.L ", +" [.5XaXaXdXaXsXdXlXgXgXgXgXdXyX&X*XJ.^ L ", +" `.&.%X4XyXyXdXdXdXfXyXyX3X&XOX{.%.L ", +" `.}.&X*X3X4X5X3X&X%XOXJ.C.^ ", +" `.}.].{.].`.Z.%. " +}; diff --git a/resources/bitmaps/lightinspector.xpm b/resources/bitmaps/lightinspector.xpm new file mode 100644 index 0000000..baef0de --- /dev/null +++ b/resources/bitmaps/lightinspector.xpm @@ -0,0 +1,192 @@ +/* XPM */ +static char *lightinspector[] = { +/* columns rows colors chars-per-pixel */ +"16 15 171 2 ", +" c #323232", +". c #343434", +"X c #373737", +"o c #3A3A3B", +"O c #3E3D3E", +"+ c #414142", +"@ c gray27", +"# c gray29", +"$ c #4E4F4F", +"% c #545353", +"& c #585858", +"* c #5D5D5D", +"= c #636263", +"- c #686768", +"; c #6D6D6D", +": c #727272", +"> c #343535", +", c #3A3A3A", +"< c #3E3E3D", +"1 c #424241", +"2 c #454546", +"3 c #494849", +"4 c #4C4C4C", +"5 c #515151", +"6 c #555656", +"7 c #5C5B5C", +"8 c #626262", +"9 c #676767", +"0 c #6D6C6D", +"q c #777777", +"w c #363737", +"e c #3A393A", +"r c gray24", +"t c #414041", +"y c #484849", +"u c #656462", +"i c #D9CFBE", +"p c #605E5A", +"a c #555555", +"s c #5D5D5C", +"d c gray40", +"f c #6D6C6C", +"g c #717271", +"h c #7C7C7C", +"j c #414141", +"k c #444444", +"l c #494848", +"z c #666562", +"x c #EEE7DB", +"c c #FFF3DC", +"v c #CBBFAB", +"b c #5B5A57", +"n c #707170", +"m c #818281", +"M c #3C3D3D", +"N c #444445", +"B c #484848", +"V c #EBE5D9", +"C c #FFF8EA", +"Z c #DBCEB7", +"A c #C8BDA8", +"S c #5E5B59", +"D c gray38", +"F c #7A7B7A", +"G c #818181", +"H c #878786", +"J c gray25", +"K c gray28", +"L c #636260", +"P c #C8BDA9", +"I c #646260", +"U c #6A6A69", +"Y c gray48", +"T c #848484", +"R c #8B8B8B", +"E c #626260", +"W c #696866", +"Q c #737272", +"! c #8E8F8E", +"~ c #4B4B4B", +"^ c #D8D1C4", +"/ c #FFF5E4", +"( c #FFF5E3", +") c #FEEFD4", +"_ c #C4B8A4", +"` c #C2B7A2", +"' c #A19889", +"] c #6A6A6A", +"[ c #828283", +"{ c #929292", +"} c #4C4D4C", +"| c gray31", +" . c #656360", +".. c #EBDEC8", +"X. c #FFF1D7", +"o. c #D9CCB5", +"O. c #A29887", +"+. c #A19886", +"@. c #948C7C", +"#. c #51504E", +"$. c #838382", +"%. c #949595", +"&. c #535453", +"*. c gray32", +"=. c #62605C", +"-. c #EADEC6", +";. c #D8CBB4", +":. c #948B7C", +">. c #444341", +",. c #717171", +"<. c #8A8B8B", +"1. c #9B9A9B", +"2. c #595A59", +"3. c #5A5A5A", +"4. c #555556", +"5. c #64625F", +"6. c #EADEC7", +"7. c #928A7B", +"8. c #434240", +"9. c gray30", +"0. c #69696A", +"q. c #838383", +"w. c #959596", +"e. c #A0A0A0", +"r. c #5F5F5F", +"t. c #636464", +"y. c gray37", +"u. c #6A6864", +"i. c #EBDEC6", +"p. c #D7CAB4", +"a. c #938A7C", +"s. c #4E4D4D", +"d. c DimGray", +"f. c gray51", +"g. c gray58", +"h. c #9E9F9F", +"j. c #A5A4A4", +"k. c #5F5E5F", +"l. c #6C6C6D", +"z. c #6D6E6D", +"x. c #706F6A", +"c. c #B3AA98", +"v. c #504F4D", +"b. c #515252", +"n. c #9E9F9E", +"m. c #A4A4A5", +"M. c gray66", +"N. c #636463", +"B. c #696868", +"V. c gray43", +"C. c gray45", +"Z. c #717071", +"A. c #656565", +"S. c gray44", +"D. c #939494", +"F. c gray62", +"G. c #A4A4A4", +"H. c #ABABAC", +"J. c #686868", +"K. c #797879", +"L. c #7D7D7E", +"P. c #818182", +"I. c #808181", +"U. c #898988", +"Y. c #949594", +"T. c #9E9D9E", +"R. c gray64", +"E. c #A7A7A7", +"W. c gray67", +"Q. c #AEAEAE", +"!. c white", +/* pixels */ +" . X o O + @ # $ % & * = - ; : ", +"> X , < 1 2 3 4 5 6 7 8 9 0 : q ", +"w e r t @ y u i p a s d f g q h ", +"e r j k l z x c v b & d n q h m ", +"M j N B u V C c Z A S D n F G H ", +"J N K L V C C c Z Z P I U Y T R ", +"k K E V C C C c Z Z Z A W Q T ! ", +"B ~ ^ / / / ( ) _ _ _ ` ' ] [ { ", +"} | ...X.X.X.o.O.O.+.@.#.9 $.%.", +"5 &.*.=.-.X.X.;.O.O.:.>.*.,.<.1.", +"a 2.3.4.5.6.X.;.O.7.8.9.0.q.w.e.", +"3.r.8 t.y.u.i.p.a.>.s.d.f.g.h.j.", +"k.t.d.l.z.9 x.c.v.b.d.G g.n.m.M.", +"N.B.V.C.q q Z.- A.S.[ D.F.G.M.H.", +"J.z.C.K.L.G P.I.P.U.Y.T.R.E.W.Q." +}; diff --git a/resources/bitmaps/logo.xpm b/resources/bitmaps/logo.xpm new file mode 100644 index 0000000..1eed8c0 --- /dev/null +++ b/resources/bitmaps/logo.xpm @@ -0,0 +1,148 @@ +/* XPM */ +static char *logo[] = { +/* columns rows colors chars-per-pixel */ +"126 126 16 1 ", +" c #000200", +". c #1A3562", +"X c #354D78", +"o c #636569", +"O c #5F6983", +"+ c #566897", +"@ c #4861A5", +"# c #7D88B6", +"$ c #7A8EDA", +"% c #A197AD", +"& c #CC99AB", +"* c #A1A8D9", +"= c #BBC5F3", +"- c #D2C7D6", +"; c #D2D4F2", +": c #F9FBFB", +/* pixels */ +"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": .X##**=***=**$*##@X :", +": .#*===**$$$$$******=$***$$+. :", +": .#*====*****#*&*$*$**;==*=**$$$$@X :", +": .#$=$=;;=;**$$$*%*%&%*#$#*==****=***$$@. :", +": X#=$$$***$$@$#*&*&&%&&&%&%%#*#*#**=*=**$$#@ :", +": X**=*$*==*&*&*&***#*#*&&%&%&%*%=###%#**=*=**$$@. :", +": X#*$=*====-&=&=&-&=%$#####%%*%&%&%*$*###$###***#$@@ :", +": O***;;;;;;*&*&;--&-&*%$@+#%%*%&%&&*&&%&%####==**$$$$$X :", +": *$=&;;;==*;&-&=&-&&&&%%%#X+#%O%%=&&%&%&%&%%***$@$###$@$@. :", +": +***&;-=&--;&=&;&&&&&-&&%&#*+@+%%-----&&%&&&%#X@@@@$@$#$@$+o :", +": #*&*&*%=&;&&%&&&&&&&&&&&%&%%#*+X+*%-&-%&%&%%##XXX@@@X@@++$@$++ :", +": X#*&*&*%*&=&&%&&&&&&&&&&&&&%%%&%;X+%%%&&&%&%+X@X@.@@@X@@@@++++$@+ :", +": X$*&*&*&-&&&&&&&&&&&&&&&&&&OOOOoo%%#O*%#%#+.X.XXXX@@+@#XXX@X@++@+@+ :", +": X$*&*&*&&&&--&=&&&&&&&&&&o&%%OOoOo%%%%OX+XXXXX+X+X@@#++#+@@X@X+++@+@+ :", +": O$*&*%=&&&-&-&-&&&&%%%&o&o&oOOOoOO%o%%*%#O%%%o%XXXXX@X+@##+@@.@@###++@+ :", +": O$***&*&*%;--&-&&%&%%O%%%OOOOOOoOo%%%%*%&o&&&&&O+@++++++X@#X@X@X+@***#$@+ :", +": X$*&*&*&&%=&&%%%%O%OOoOOOoOOO%%+OO##%o%#%o%ooo&%XX+++X#+X@+X+.XXX@##*###@@+ :", +": X$*&*&=&*%-&&%&#%OOOOoOoOOOOOO;-%O;%%O%#%%*%%o&%#XXX+++XXX@X@X@@@@@@$*=*##+@+ :", +": #=&=&*%&%*%%#%##OO+OXOXOoOXOoO+#O%*%#%#*#%%%%%%%XX.XX+@+++@@X+@@X@X+#**=#$@+@X :", +": +=&;&*&*%*%%##O#+OOOOOXOOOOOOOOOO#%%#%%=%*%*#%%%X+X+XX.XX+X@@O#++@@+@+*===##@+@X :", +": X*&;&*&*##+#O#OO+O+O+O##+#O#OOoO%#**%%#%%*%*####XXX@XXXXXX.@@+++X#@XX@X+#=%$+#@@XX :", +": **=&***####+#+O+O#*#$##%##*#*##O***%=%%%*%*##OO.X.XX++#+#*+X+X+@@@#X@@+X+#*#$@$+OO :", +": $**&*###$+++###+##*#*+####*#*##O;#*%-%%O%%*##OX...X.....OXXX@XXX+#*=#XXX++##*++@#+O :", +": O$***#$@$+$#***+%==**###=OO+O+*#;;;-%O%%-%-%;-X+#+X+%XXXO###+X++++#*:;*+++@+#@#@+@++O :", +": .$*$*$$@$+#$*$***#***##%%%OoOXX+#-:;#X%%%%O#--O++++##%=+#O=%*%;-:--##=;;;##X+@@+@X+@++o :", +": #$$*$$+##$*=*;**=;==##*:-:;:OOXO%---%%%-%--*%##OX++%#-==XO#;**%-%:;;**=:;**#++@#+@@+@OoO :", +": =$$@$@$*##*@#%++;%#+O+;-;=;;%XOO%-:;-%%%---%;:-OX+*#X%=##+#%#=;%%O##=;:;;O#+#*#+#+@@++O. :", +": =$$$$@$**#$##+$#*=*#O##-;*--;%OO#%;::=*-:-;*-%;+X#=;:#%####X**#OXX@XX*-;:=*+$**++X$#@@++O :", +": .$$@$@$$*@$+##$%;***%##O*###*%;%%#-%-::%-%%=--OO++=+#;;%O#*O%;;%#XXXXX+#=*=**#=#+@+#*+@++Xo :", +": #$$$@$*$@$#*#-#::;====%O%*###-=O#*#%-::-*=%--;%*##+#X::-X*=;%;OO++.X.X.##O**X+#*###+++@$@OXX :", +": =$$@-*$@$@$#;@#::-;%;#*%;%%-:::-;%#%:--O;----;-#OXX#%-:--O*-#XXXXXX.X.XX+XXO+@+*;%#++@+++XO :", +": O$$@*;$@$@@#+@##::=;::;-;*-=;-;;*-%-;--%::;:;;---*OX#O%-%*O++X..@XXX+XXXXXO++X+@%=*##++++++XO :", +": $$$==$@@@@@#@@+#+#+*-:-;%%O*%-%%%=--o--%%%%;-%%-=$##+#O#+#XX....XX.+X+++@+X#XXXXX###+#+++++OX :", +": .$$:*$@$+@@@$+X$@#+#**#=*=%%#***#*%:%=-%%*;;*;**%:;**==*##+++XX@..+++X+$#+XO+#+XX+#*****+++#+O :", +": +$*$$@$@@@@X@@@#*#+XXX##==*##%#**%--;;%%%%;%-;%#%--%;==#++-O+XXXX.+XXX+.++@X+++@+X##*****X+++Xo :", +": $$*$@$$$@$#@@+X$#*++X+X%**#%%=O%*;-;::;;%*;--*#*%-XO;#%$+%--O+X.X@XXXX.X.XX+#%+OX#O#+##=*#+++OoO :", +": **=$$@$#*=#X+@X@*#+@+@X+*%%%%%#%#%**:-:*%---%O#**#OX#%+#=%-=%OX.X+#+#.X.XXXX+OXX###X+O#*=#+X+XO :", +": +$=$$$*#*#+X@X#X$*+@+@+X#---;-#%***#;-:-;-:--####O%X++OO#*-=#+*+#XO#+XXX@X+X+XXXO##+++++***##+OXo :", +": #=$$$$#**-=+++#**++*#+O#O=--%%O;%***-:--;;-:%*##OOXXXXo*o%--#+O*++XXX....XXX.XXO%#OXX++#****O+XOX :", +": =*$@$;;;=%+#+#$#$@;;$X##*-:-%*%*%O%%;:::::;%%O%#O++XXX%%%o%=*+O=;*+XX.@X@X@X++*+O++XXX##;***#X++O :", +": =$$$@=##@@.@++XX++#=+X@O%---ooO*#%o#%:-:::=%OO#O++XX.XX%ooO#X++#%=+.X+XXXXXXXO+X.X.X.+X#**###OXXXo :", +": o=$$$=*@X+**#**#+#*;#O+#+-o%o%o%%%%%%-;:-;;%OO#OX+X+X+XXooo#XX+#++OXXOO-..XXXXX+.XX+++X++*###*#OXOX :", +": #*$$$$@@*;;==%*#;*++X.@X+%oo%o&%*%%%-==%-%%o%oOX+@###XXXooo#X..#*#O.=.+%X...@.XXXX++#X#X++#+O++++oO :", +": #$@$$$$**:;:;*%*%+X@.XX+X&%&%%%*%=%-%=%*%%oooOOO+++$XX.Oooo#.X.X.X%++X.+++.@.XX#XXXXX+++X+##X+X##OXo :", +": $$$@@+*$#;;-=-*XX.X.X.+.XO%o%o%%&o%%-%%%%oooo#OX+X+@X.XXoooX...XX..XXX+.X+XX@.XXXXX.X.++XX+X+X++#oO :", +": $$@$@$***;;*+@X@X@XX.X++#+%&o%o&o&&&%-&ooooOO%OOX+X+XX.OoooX.....X...+++X#X.X@.O.XX@XXX$X+XXX++++OOO :", +": $@@@$#$#*=;#$@XX+X+@++**=#*#&%oo&o&o&%&oooooOoOOOX+XX.XXooo...........X++++.X.X++.+.X.+XXXX.X@XX++X :", +": $$@$@$$$*=%+@$#;=*$***=====*%&o&o&&&&&o&oooOOO+O++XOXX.XXo..X#OOX@XX...X+%@+XX.++X...XXXXXX@X@+++OXo :", +": $$$@$@@###;X+#;;;-*++#=*;;;;-&&oo&&o&%%ooo%oo++++XXXX.X.X...X.XX..OO%#+...#.XXX+XX+.X.OXXX#+XX@XXXX :", +": $$$$$@@$@$=+@===#@X@+=*=;;%*#&%&o&&&o-----o%oO++++XXXX.XXOXX...X.X...O++XX*X+X.#X#+X.@X+X$*#@+X+X+Xo :", +": =@$$@@@@$.+#@+#.@X..XX++#XXXO%-o&o&&-%--%%oooX++*++X+XX.+.X.X...XXX+X...XX;=O.X###+...XX++#.XX+X+XX :", +": *$@$@@@@@+@@.@@@@@.@.@.XXXXOX%-:--&&&----%oOoOX#%*XOXXX+XX.X...@.....X.@OX#;##X+#*X+.@.@#+#@X+++X+Xo :", +": *$@@@@@@##;@@@@XX.@...X...+XX#-*-&-&o%-%%o%o#+OX+++XX..XX................+XO**#.X#++X.X@+X+XXX+XXXX :", +": $$@$@$@##=++X##+@+%+X@.X.@.XX++*%-&-&&%-%-oO+$+OX+XXXX.X.X.X.O.OO+.OX....X+X.*;#.+#*+XX.XX...+++XOXo :", +": $$@@$@@@$@*##+*##@##*@+@X.X.XXO+%%%%----%%%O++#OOX++X.X.XX+X+...X...X......+X.#;#.+$+XX.+...XXX@+XX :", +": $$@$@@@$#$+@#$#***%%++++++X@XX+Oo&-;-:-:;:##+*+*+OX#XXXXX#X..X.X..X@.XXX...X#O.*;O.+.+..+@.+X+X++O.O :", +": $$$@@+$@@#*@+$$#;;:;==**###+#+##=#-%:-::-%#####+#O#+XXO.XX+.....XXX.X.XXX..XXO+.%=X.....#..XX@+X++X :", +": +$$$@$*$#$$$@@@$;;::;:;;=:*O%%%**;:;---:::::%*+#*;-*O==*#=+X.XXO.oXO+O+OO%.X.o++.**..XX.+X.X.+X++O.o :", +": +$**=*=**+@@@@#@+=;;:==**#*@X.XXXX-;:;;%:::::;:;:;:::;;#$*#X+.XOOO#+O#XX#O..X.XXX+;+.X@.+X..XX@X++O :", +": X$**$==;*$@@++X@@@#;;;;=#$##@XXXXXXO#*;;=:::::::::::::;#+###+X.XX++OOO##O-#XX+XX.X=-.+X.X++X.@+++O o :", +": $=$*$;;*$$.@@###OX#=*$##++XXX@.#%=+XO#%;;:::::::::::::;;%*+*+..XX#+*##+X%%+..XX..#;XX+.X@XXX@#++Xo :", +": **$@**:*=+@#$+**=#X+=**#$++X++++#X+XO+##*;:::::;:::::::::;*##+OX#+++#+OOO*%XXXO+XX=##XX.XX+X#++Xo :", +": @=$$**;==#@++##=%=+#%#@$@@@X.XX#+#+#%%XO+*=:::::;::::::::;;;OO+O..+##*%#%=%O.X+#.X*;#X.X.X@++++XX :", +": +*=***;;;#++**;;;*;%+%;OX.X..X####**;*$+#+#*;::;:::::::::*=##+O+.X%#=%*%:;;OXXo#XXO;;X@XX.@X+++Xo :", +": *===:;;;+X++#+++##-#%%%...X.X+*#*%##*%O#+XO*;;:::::::::=##*#O O#*O#%*#*=%%%XO.*XX+==++X.+.XX+XX :", +": +=$*;:;;+#*=O#XX+OX%%#-;X..#XO**#*==--##XOX=:;::::::::;=***%+#+*=*%***#*#*+OXX*OX#=;##X+XXX+X+.O :", +": X*=*;::=$@$@%#;-;;:-%*#%%...#X-X+#*;;-;OOOO%:::::::::=*##+X..XO+*%*%%%=**#OO#+*+##=*#+XXXXXXXXX :", +": ***;::;++@@=;***;;:::%**O.XX#OOX#*;;:;:-%;:::::::::;;#=%OXOXO+##;%##;*;=*#OX**#+**=*$+#.++XXoX :", +": #$@*;:;$#+%*=+++###;::%**X.X++XO#%%::;::::::::::;;%*-;;=%#OO+OO#%O+=%==*+O%=*##$****##+X.+XX :", +": X$$$=::*=*###+######=::;;*%X#XO===:%:::::::::::;;=*##%==*OOOOO##=OO=;;:*;;;;=**#=****##XX+OXX :", +": $$$$:;;=*X+#*#%#*###;;:-;;+#%%;=;:-%:;:::::;:;=###;#%%*#OXOO#%*%#O#=:;;;;==*;***=$**$#+.+XX. :", +": +$@$;:;;++*=#*#*#-#*=*::*:##=%*;;;-;;:;:;;;;*%O=OO%%#%OOOO+#O%X#%%+==;==#*#=======***$#+XXXX :", +": $$#;;:=$*;%*#**;#*##+-:;;=+=*;;;=;;:;:;==**=%=%%#*##X*#O+OO%XX##+*=**=##X#*==;==*#####+OX. :", +": +$$;:;;$*==#$*=***####:;;=$*:=;*=;;;:;;;:;;=;=*#***#%#%#OOO%oO#+#**#**O++#**;==**#OX##OXX :", +": $*=;=;=+;;$+***#*+*+#;:;;#=;*=*;=*;;=#;-=*==**=%=**%*#%OOO%XXX##*#O++.O+O#**=**##+XXOXo :", +": X$===;==;;++=;*$#*#$=:::;==;**==*;;;*==*#=**==;=*##*+%OO#%#%OO##+#OOXX.O.O#$#*##+OXO+o.. :", +": #$==;=*;=++=;;%##**;;:;;$=;==#+#===;==*****=****#**OoOO*#=;*+O+#+++XX..+X####+@XXX+O. :", +": X$**=*$=;#@#;;;**#*;:;:;**==;*#XO*==;*;;;=*O#%;**=##-%*#*%%#*#O+OXO...X+#+##$+XXXXOXX :", +": +$==*#==*@+*;;;=*%*=:;;*;*=;;#=#=*;==;;=;++%*#*=%O#%-=*###*=#+OXX..X+@###+#++XXX+XO :", +": $$*$$*;=*#**:;;=#**;:;;=*=;=*O;====;;**=$%=*-*##*O;;;*%#=**##XXX++$#*####++++X+XO :", +": *$*@#*==**=*;==##*=;;;;=*$***O*=;;=*#%##=*;**#+#;==#*%*O####+X##*$*$*$#++@XX+@+. :", +": X=*$@$***=;;=;=$+*#*===;;=***=;;;:==#*=;==*;**=;;=###*##+#+##$$*******##++++++XX :", +": @=*@@$$#$*;**=$+++##***;;;;;;:;;=$#*===;;;=*=;;=#**=**#*#$#$$***$*$*#$##+++++X :", +": .$**$@$+$$=;**=*O+$+*===;;:;:::;*##=;=;;;;:;;=;*===#O+*=*******$*$***$$##+$+o :", +": @***$*+@@;=****#**=**;=;;;;;;;;;=;;;;:;;;;=*#*=*####*=****$***#$$*$$#$#+@X :", +": .$**===**$;;=***==;;;;;;;;:;;;:;:;:;:;:;;;;;;;;;=*=*=*=#***#*#*#$$$#$##+o :", +": .#$$**=**=;==*==;;;;;;;===;;;;:;:;:;;;;=;;:;:;:=**==**===*=**$*$$##@++X :", +": .$******=;;;;;;;;;;;;;==;;;;:;:;;;;=:;;;:;:;:;;;;==*=====*=**$$#$##+o :", +": +$$****;=;;;;;;;;;=;=;;===;;;;=;;:;:;:;;;;;;;;;**=*=*=**$$##+#++@X :", +": +$$*===;;;;;;;;;;;;;=;;;=;;:;:;:;:;:;:;;;:;;;;;===*=*=**#++$@$+X :", +": X$*===*==;;;;;;;;;==;;;;;:;:;:;:;:;;;;;;;;=;====*=*=**$$#$@+@X :", +": .$****=======;;;=;=;;:;:;:;:;:;;;;;;;;;;;;====***=****#$$#@. :", +": +$*$$*======;;==;;;;:;;;;;;;;;;;;=;=;====*=***=**$$#$++X :", +": .$$$$$=*====;===;;;;;;;;;;;;;=;=;=;======*=*=****#$++. :", +": X$$$$=*========;;;;;;;=;=;========*=*=*=****$$+$@X :", +": @$$*==========;=;=;=;==========*=******$*$$@+X. :", +": X$$**=$=*=*=============*=***=****$$$$#$@X :", +": X@$$**=***=======*=*=*=******$*$$$$$+.. :", +": @@$$*$*$*******$***$***$*$$#$@$X. :", +": X@$$$$***$*******$*$$$$@+X. :", +": .@@@@$@$$$$$@$@+XX :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +": :", +"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::" +}; diff --git a/resources/bitmaps/modify_edges.xpm b/resources/bitmaps/modify_edges.xpm new file mode 100644 index 0000000..9279005 --- /dev/null +++ b/resources/bitmaps/modify_edges.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *modify_edges[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #00C000", +/* pixels */ +" ", +" ........... ", +" . .. ", +" . . . ", +" XXXXXXXXXXX . ", +" XXXXXXXXXXX . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . .. ", +" ........... ", +" " +}; diff --git a/resources/bitmaps/modify_faces.xpm b/resources/bitmaps/modify_faces.xpm new file mode 100644 index 0000000..f948e2b --- /dev/null +++ b/resources/bitmaps/modify_faces.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *modify_faces[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #00C000", +/* pixels */ +" ", +" ........... ", +" . .. ", +" . . . ", +" ........... . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX. . ", +" .XXXXXXXXX.. ", +" ........... ", +" " +}; diff --git a/resources/bitmaps/modify_vertices.xpm b/resources/bitmaps/modify_vertices.xpm new file mode 100644 index 0000000..8f73d5a --- /dev/null +++ b/resources/bitmaps/modify_vertices.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *modify_vertices[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #00C000", +/* pixels */ +" ", +" ........... ", +" . .. ", +" . XXX . ", +" .........XXX . ", +" . XXX . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . . . ", +" . .. ", +" ........... ", +" " +}; diff --git a/resources/bitmaps/noFalloff.xpm b/resources/bitmaps/noFalloff.xpm new file mode 100644 index 0000000..7225685 --- /dev/null +++ b/resources/bitmaps/noFalloff.xpm @@ -0,0 +1,15 @@ +/* XPM */ +static char *noFalloff[] = { +/* columns rows colors chars-per-pixel */ +"64 8 1 1 ", +" c white", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" " +}; diff --git a/resources/bitmaps/notex.tga b/resources/bitmaps/notex.tga new file mode 100644 index 0000000000000000000000000000000000000000..594a463ca681da0301b0e9eee0c11f05203d64af GIT binary patch literal 5036 zcma*pdrZ~m9mnx=j-aKMq>GVgXOoT?nviHVx+O;AbS`K}=FC(~)6lFn(FyZ{tx6G) z1A-zKs{$fkcIsu~rGf$j6vZm0PytcA;H6T$;K1p;)ouTM_Pn28fkT%}NJyUF^ZTCP z@B4c$-{<9Wz3%G$o9deCdgnD?n%5C<3aoB4%1b$x`gHv}o0tALEna}*x#63=l3 z2Wv{yFSM2mwrJ}*vQwp{ zZ!2YQqH3N#(wB#aDxF`AT5;*D&ee}oQc0Rlx7O)GTfJ^PYSWrE?`v^ssZRCusGwkk z+_Pq>xT{O&&-+x~)THlTbSgLZr*co4q~)!x`ugkHHqD9E)jE0dx3)dy#0gbbPgG8S zlHQCk?S6VmMMWNY)5mD*qX#NIGFs#k-*iGZZp0|l#O#0Gp!_XvrEz0OT3WEO?%mRn zBSV#zZvN>VF}bEjjg8UvRWU6s+PynOt}$bj^WcGUaz^*1(9#lP+tH1UDk*u#wimRu zX~&K?Z9DeL6%`h~?w@Azra$PL7kjiW)1@^VCaL=6F*7YLo95Dn3SGN8SILDjYJ0ZR zq-**~Xfz+U_t`hf&o_yijx~RY*Ri_&D!6=E4GnV@5)z_}J9o_32FPW`t>VQoGl|ah z{<5ONe2f#5($Qg_IMjYjUe|ra#}AP!G*p}Z-l4d-0RKckIh3ict|T*=F0C>P;Nin; ztxYGPV6E$HS4H_qEicX0y?ZNdjFV{Mj#%9^sjtfnm)A^qbMr@9Z&u#2YuC)ojFM~o zcpZ3FqiNGz3QS1Q{rlM(GsanipR~7|1u;^t@Nn&UQm3dWw_O~ko*z*}gjt%=(K_?& zpvI3k3$t(H-(7s>kgi^tW5;Fj37>A=`q;Ktl^2+Hyh%DlOU{+4?b1x!pK{_0Gu_!b zcDv0cpPMsH)&HqhZq5*`EX`9}+X^$DW{2~)Pg@(E`(&y8~iyzxb9am6b+VM8V zHF>hCzpqeiY^*X)o>b4%eOk0=k23JVL;dwF@< zXGZt-?d@g?-%`ZJjXG{psjRFt>-IyNK7++B|ERoNr%rhl85ybAswy2nzFKi+7OJYY z>4OhE&Zyb~;Yl;2miOR09XvQx>sWH`%>AimDqYIrDvd|FBereRym@Zz;L7=(*G&9@ z@9VW?vs;-&dc12xwX5f@_U#LFe(M_;y|t=X2M$b;EmQWMkKg^MwUNGng%E8H~5(&6=RB^?^2) z#lFGGI&X&AZSN^K_=k`6yftHn`B06$h ztYeQJDJ#ot-9ePay#Dl3*Z)dK|JA7N#jn{kJ~dVO^5xmeV4hxlrA?nZ6(z0XiW$*2 zM5XC1ktw;6I{osnHe@+Vb8&K<-ZV?6_QhdMnBY_w`ohA(^u<3L6dmmrZ+=xd^h+bYTv1Be`bV`tDYE@6pdF7pv};#@lKe{}qh~g8cXx^Q>=|oc?J|XM zxv9XNoh3?0aQZwkF=~0d)1+xqHuqmu1pU%mQng(j9kF)gn5^9U-T#dGeOa&h^F2E+PS2u62v@2~erOz)t zMysjm*EWq+<~3KZ%(q+O&$YWv+RK!`eUW@-nfLUp{huom#qd zhyoTYP-0?`9i_}p`i5y-S((nBo#~`Lh;rwbUb3mct*w=|#MRWyc8(7-Dk-1O)WNF_ zYHavag}Yap`$cBA4pV>Wb1SiUq0U~c)p@gRDl4bk*9R=pRtNY-9%aJe-6MYwJ zVI|CjoiG%Z!c^D_V-bV7uot6>#V{E*!)REI80?1Ou$*JMX*MmahxxD{T;Wr$I=NQx8Qi%6(UvG`UgSZeM;zYcN8}TEKr1n@@I_V9jEaEQw zg~RX|6N%4o8eXH1*}!pl4%hXTKxz%{!+*r!L0pIraUx!%A3x$qJn5I-tI8qX!d>_a zhmjgC!)G{+`}E;A9Eax^z23ry_i!KnV>Pk^a3Ma#i3o`F=*N+G(l5Qi6hYPz{=#7x z4VU4w-px!Oe#3G02r;koFL!J!{KwfwD!33I;zYd2=g^NM@uXjRgDHKC4*tSncnp^@ zYB&wA;WqrnYw#Sd<8}Te4*%gmJctYNA)kO3aU=0KlF{@_Z!qN!ci}G_Myj|BpW!sT zMjw8|ad-~b-Xc{pC0wQ$nmcK0)V?W AfdBvi literal 0 HcmV?d00001 diff --git a/resources/bitmaps/paste.xpm b/resources/bitmaps/paste.xpm new file mode 100644 index 0000000..3bfcc0b --- /dev/null +++ b/resources/bitmaps/paste.xpm @@ -0,0 +1,33 @@ +/* XPM */ +static char *paste[] = { +/* columns rows colors chars-per-pixel */ +"20 19 8 1 ", +" c None", +". c black", +"X c #808000", +"o c yellow", +"O c navy", +"+ c #808080", +"@ c #D7D0C8", +"# c white", +/* pixels */ +" ", +" ", +" ", +" .... ", +" .....oo..... ", +" .X+X.o..o.+X+. ", +" .++.@@@@@@.+X. ", +" .X#........++. ", +" .+X+X+X+X+X+X. ", +" .X+X+XOOOOOOO. ", +" .+X+X+O#####OO ", +" .X+X+XO#####O#O ", +" .+X+X+O#...#OOOO ", +" .X+X+XO########O ", +" .+X+X+O#......#O ", +" .....O########O ", +" OOOOOOOOOO ", +" ", +" " +}; diff --git a/resources/bitmaps/patch_bend.xpm b/resources/bitmaps/patch_bend.xpm new file mode 100644 index 0000000..4eee0e3 --- /dev/null +++ b/resources/bitmaps/patch_bend.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *patch_bend[] = { +/* columns rows colors chars-per-pixel */ +"16 15 4 1 ", +" c None", +". c black", +"X c #000088", +"o c gray80", +/* pixels */ +" ..... ", +" .oooo... ", +" .ooo..ooo. ", +" ....oooo... ", +" ..oo.ooo. ", +" ..oooo. ", +" X .ooo... ", +" XXX .o.oo. ", +" X ..ooo. ", +" X .ooo. ", +" X X .ooo. ", +" XXXXXXX .oo. ", +" XX X ... ", +" ", +" " +}; diff --git a/resources/bitmaps/patch_drilldown.xpm b/resources/bitmaps/patch_drilldown.xpm new file mode 100644 index 0000000..8288f91 --- /dev/null +++ b/resources/bitmaps/patch_drilldown.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *patch_drilldown[] = { +/* columns rows colors chars-per-pixel */ +"16 15 4 1 ", +" c None", +". c #880000", +"X c #000088", +"o c blue", +/* pixels */ +" ", +" oXo ", +" XXX ", +" oXo ", +" . ", +" . ", +" oXo ", +" XXX ", +" oXo ", +" . ", +" oXo ", +" XXX ", +" oXo ", +" . ", +" " +}; diff --git a/resources/bitmaps/patch_insdel.xpm b/resources/bitmaps/patch_insdel.xpm new file mode 100644 index 0000000..b123c12 --- /dev/null +++ b/resources/bitmaps/patch_insdel.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *patch_insdel[] = { +/* columns rows colors chars-per-pixel */ +"16 15 4 1 ", +" c None", +". c black", +"X c #880000", +"o c #000088", +/* pixels */ +" ", +" ", +" ", +" oooooooo ", +" o ", +" o ", +" ooooo ", +" ooo ", +" ooo o ooo ", +" XoooXXXXXXoooX", +" ooo ooo ", +" o ", +" . . o o ", +" . oooo ", +" . . o " +}; diff --git a/resources/bitmaps/patch_showboundingbox.xpm b/resources/bitmaps/patch_showboundingbox.xpm new file mode 100644 index 0000000..a1c9e6d --- /dev/null +++ b/resources/bitmaps/patch_showboundingbox.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *patch_showboundingbox[] = { +/* columns rows colors chars-per-pixel */ +"16 15 4 1 ", +" c None", +". c #880000", +"X c red", +"o c gray80", +/* pixels */ +" ", +" .............. ", +" .XoXoXoXoXoXo. ", +" .oooooooooooX. ", +" .Xoooooo..ooo. ", +" .ooooo..ooooX. ", +" .Xooo.ooooooo. ", +" .ooo.oooooooX. ", +" .Xoo.oooooooo. ", +" .ooo.oooooooX. ", +" .Xooo..oooooo. ", +" .oooooo..oooX. ", +" .XoXoXoXoXoXo. ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/patch_weld.xpm b/resources/bitmaps/patch_weld.xpm new file mode 100644 index 0000000..03fe917 --- /dev/null +++ b/resources/bitmaps/patch_weld.xpm @@ -0,0 +1,26 @@ +/* XPM */ +static char *patch_weld[] = { +/* columns rows colors chars-per-pixel */ +"16 15 5 1 ", +" c None", +". c black", +"X c #880000", +"o c #000088", +"O c blue", +/* pixels */ +" X ", +" X ", +" X ", +" XX O O ", +" X X o ", +" XX ooo ", +" X O ooo O ", +" X Xo ", +" X X ..... ", +" X O .... ", +" X ... ", +" X X .. . ", +" X . . ", +" X ", +" " +}; diff --git a/resources/bitmaps/patch_wireframe.xpm b/resources/bitmaps/patch_wireframe.xpm new file mode 100644 index 0000000..8d8024e --- /dev/null +++ b/resources/bitmaps/patch_wireframe.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char *patch_wireframe[] = { +/* columns rows colors chars-per-pixel */ +"16 15 3 1 ", +" c None", +". c #880000", +"X c gray80", +/* pixels */ +" ", +" ", +" ", +" . ", +" .X. ", +" .XX.. ", +" ..X.XX. ", +" ...XX.XX.X. ", +" .XX.X.X..XXX. ", +" .XX...XX.X.X. ", +" ..XXX..XXX. ", +" .XX...XX. ", +" ..XX... ", +" .X. ", +" . " +}; diff --git a/resources/bitmaps/popup_selection.xpm b/resources/bitmaps/popup_selection.xpm new file mode 100644 index 0000000..d69578d --- /dev/null +++ b/resources/bitmaps/popup_selection.xpm @@ -0,0 +1,28 @@ +/* XPM */ +static char *popup_selection[] = { +/* columns rows colors chars-per-pixel */ +"16 15 7 1 ", +" c None", +". c black", +"X c #880000", +"o c red", +"O c cyan", +"+ c #888888", +"@ c white", +/* pixels */ +" ", +" oo oo oo oo ", +" O ", +" @ ", +" o @ O o ", +" o @ o ", +" O O XXXX ", +" @ XXX ", +" o XX o ", +" o @X o ", +" ", +" ", +" oo oo oo ...", +" .++", +" .++" +}; diff --git a/resources/bitmaps/redo.xpm b/resources/bitmaps/redo.xpm new file mode 100644 index 0000000..3f45d82 --- /dev/null +++ b/resources/bitmaps/redo.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char *redo[] = { +/* columns rows colors chars-per-pixel */ +"16 16 2 1 ", +" c None", +". c navy", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" .... ", +" . .. . ", +" . . .. ", +" . ... ", +" . .... ", +" . ..... ", +" . ", +" ", +" ", +" ", +" " +}; diff --git a/resources/bitmaps/refresh_models.xpm b/resources/bitmaps/refresh_models.xpm new file mode 100644 index 0000000..6af2b92 --- /dev/null +++ b/resources/bitmaps/refresh_models.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *refresh_models[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c #C00000", +"X c #FFC000", +/* pixels */ +" ...... .", +" ..XXXXXX.. ..", +" .XXXXXXXXXX..X.", +" .X.....XXXXXXX.", +".X. ..XXXXX.", +".. .XXXX.", +". .XXXX.", +"........ .XXXXX.", +".XXXXX. ........", +".XXXX. .", +".XXXX. ..", +".XXXXX.. .X.", +".XXXXXXX.....X. ", +".X..XXXXXXXXXX. ", +".. ..XXXXXX.. ", +". ...... " +}; diff --git a/resources/bitmaps/scalelockx.xpm b/resources/bitmaps/scalelockx.xpm new file mode 100644 index 0000000..9a64cfb --- /dev/null +++ b/resources/bitmaps/scalelockx.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *scalelockx[] = { +/* columns rows colors chars-per-pixel */ +"16 15 4 1 ", +" c None", +". c black", +"X c blue", +"o c white", +/* pixels */ +" ", +" .............. ", +" .oooooooooooo. ", +" .oooooooooooo. ", +" .oooXXooXXooo. ", +" .oooXXooXXooo. ", +" .ooooXXXXoooo. ", +" .oooooXXooooo. ", +" .ooooXXXXoooo. ", +" .oooXXooXXooo. ", +" .oooXXooXXooo. ", +" .oooooooooooo. ", +" .oooooooooooo. ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/scalelocky.xpm b/resources/bitmaps/scalelocky.xpm new file mode 100644 index 0000000..a8e72ad --- /dev/null +++ b/resources/bitmaps/scalelocky.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *scalelocky[] = { +/* columns rows colors chars-per-pixel */ +"16 15 4 1 ", +" c None", +". c black", +"X c blue", +"o c white", +/* pixels */ +" ", +" .............. ", +" .oooooooooooo. ", +" .oooooooooooo. ", +" .ooXXooooXXoo. ", +" .ooXXooooXXoo. ", +" .oooXXooXXooo. ", +" .ooooXXXXoooo. ", +" .oooooXXooooo. ", +" .oooooXXooooo. ", +" .oooooXXooooo. ", +" .oooooXXooooo. ", +" .oooooooooooo. ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/scalelockz.xpm b/resources/bitmaps/scalelockz.xpm new file mode 100644 index 0000000..bb4f5c2 --- /dev/null +++ b/resources/bitmaps/scalelockz.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *scalelockz[] = { +/* columns rows colors chars-per-pixel */ +"16 15 4 1 ", +" c None", +". c black", +"X c blue", +"o c white", +/* pixels */ +" ", +" .............. ", +" .oooooooooooo. ", +" .oooooooooooo. ", +" .oooXXXXXoooo. ", +" .ooooooXXoooo. ", +" .oooooXXooooo. ", +" .ooooXXoooooo. ", +" .ooooXXoooooo. ", +" .oooXXooooooo. ", +" .oooXXXXXoooo. ", +" .oooooooooooo. ", +" .oooooooooooo. ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/select_mouseresize.xpm b/resources/bitmaps/select_mouseresize.xpm new file mode 100644 index 0000000..59e0e1f --- /dev/null +++ b/resources/bitmaps/select_mouseresize.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char *select_mouseresize[] = { +/* columns rows colors chars-per-pixel */ +"16 16 2 1 ", +" c None", +". c black", +/* pixels */ +" ", +" ... ... ... ", +" . ", +" . . ", +" . . ", +" . ", +" . ", +" . . ", +" . . ", +" . ", +" . ", +" . . ", +" . . ", +" . ", +" ... ... ... ", +" " +}; diff --git a/resources/bitmaps/select_mouserotate.xpm b/resources/bitmaps/select_mouserotate.xpm new file mode 100644 index 0000000..0dc20b5 --- /dev/null +++ b/resources/bitmaps/select_mouserotate.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *select_mouserotate[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c blue", +/* pixels */ +" ", +" XXXX ", +" XX XX ", +" X X ", +" X ........ X ", +" X . . X ", +"X . . X ", +"X . .XXXXX", +"X . . XXX ", +"X . . X ", +" X . . ", +" X ........ ", +" X ", +" XX ", +" XX ", +" " +}; diff --git a/resources/bitmaps/select_mousescale.xpm b/resources/bitmaps/select_mousescale.xpm new file mode 100644 index 0000000..da001f7 --- /dev/null +++ b/resources/bitmaps/select_mousescale.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *select_mousescale[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c blue", +/* pixels */ +" ", +" XXX XXX XXX ", +" ", +" X XXXXX X ", +" X XXXX X ", +" X XXX X ", +" X XX ", +" ........ X X ", +" . . X ", +" . . X ", +" . . ", +" . . X ", +" . . X ", +" . . X ", +" ........ XXX ", +" " +}; diff --git a/resources/bitmaps/select_mousetranslate.xpm b/resources/bitmaps/select_mousetranslate.xpm new file mode 100644 index 0000000..553b09d --- /dev/null +++ b/resources/bitmaps/select_mousetranslate.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *select_mousetranslate[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c blue", +/* pixels */ +" ", +" ....... ", +" . . ", +" . . ", +" . . X ", +" . . XX ", +" . . XXX ", +" . .XXXXXXX ", +" . . XXX ", +" . . XX ", +" . . X ", +" . . ", +" . . ", +" . . ", +" ....... ", +" " +}; diff --git a/resources/bitmaps/selection_csgmerge.xpm b/resources/bitmaps/selection_csgmerge.xpm new file mode 100644 index 0000000..aedb442 --- /dev/null +++ b/resources/bitmaps/selection_csgmerge.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *selection_csgmerge[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #00C0FF", +/* pixels */ +" ", +"..... XXXXX", +". . X X", +". . X X", +". . X X X", +". . XX X X", +". . XXX X X", +"..... XXXX X X", +". . XXX X X", +". . XX X X", +". . X X X", +". . X X", +". . X X", +". . X X", +"..... XXXXX", +" " +}; diff --git a/resources/bitmaps/selection_csgsubtract.xpm b/resources/bitmaps/selection_csgsubtract.xpm new file mode 100644 index 0000000..07545e0 --- /dev/null +++ b/resources/bitmaps/selection_csgsubtract.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *selection_csgsubtract[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #00C0FF", +/* pixels */ +" ", +" .............. ", +" . . . ", +" . . . ", +" ........... . ", +" . . X X X. . ", +" . .X X X . . ", +" . . X X X. . ", +" . .X X X . . ", +" . . X X X. . ", +" . .X X X . . ", +" . ........... ", +" . . . ", +" . . . ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/selection_makehollow.xpm b/resources/bitmaps/selection_makehollow.xpm new file mode 100644 index 0000000..6b5ecf2 --- /dev/null +++ b/resources/bitmaps/selection_makehollow.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *selection_makehollow[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #00C0FF", +/* pixels */ +" ", +" .............. ", +" . . ", +" . XX XX XX X . ", +" . X . ", +" . X . ", +" . X X . ", +" . X . ", +" . X . ", +" . X X . ", +" . X . ", +" . X . ", +" . X XX XX XX . ", +" . . ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/selection_makeroom.xpm b/resources/bitmaps/selection_makeroom.xpm new file mode 100644 index 0000000..7594995 --- /dev/null +++ b/resources/bitmaps/selection_makeroom.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *selection_makeroom[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #00C0FF", +/* pixels */ +" ", +" XXX XXX XXX XX ", +" X X ", +" X .......... ", +" . . X ", +" X . . X ", +" X . . X ", +" X . . ", +" . . X ", +" X . . X ", +" X . . X ", +" X . . ", +" .......... X ", +" X X ", +" XX XXX XXX XXX ", +" " +}; diff --git a/resources/bitmaps/selection_selectcompletetall.xpm b/resources/bitmaps/selection_selectcompletetall.xpm new file mode 100644 index 0000000..158728a --- /dev/null +++ b/resources/bitmaps/selection_selectcompletetall.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char *selection_selectcompletetall[] = { +/* columns rows colors chars-per-pixel */ +"16 16 2 1 ", +" c None", +". c #C00000", +/* pixels */ +" ", +" .............. ", +" . . ", +" . . ", +" . . ", +" .............. ", +" . . . . ", +" . . .... . . ", +" . . . . . . ", +" . . . . . . ", +" . . . . . . ", +" . . . . . . ", +" . . . . . . ", +" . . . . . . ", +" .... .... .... ", +" " +}; diff --git a/resources/bitmaps/selection_selectinside.xpm b/resources/bitmaps/selection_selectinside.xpm new file mode 100644 index 0000000..dc12e9d --- /dev/null +++ b/resources/bitmaps/selection_selectinside.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *selection_selectinside[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #C00000", +/* pixels */ +" ", +" XX XX XX .... ", +" X . . ", +" X XXXXX X . . ", +" X X X . . ", +" X X X . . ", +" X X X X . . ", +" X XXXXX . . ", +" X . . ", +" X XXXX X . . ", +" X X X . . ", +" X X X . . ", +" X XXXX X . . ", +" X . . ", +" XX XX XX .... ", +" " +}; diff --git a/resources/bitmaps/selection_selectpartialtall.xpm b/resources/bitmaps/selection_selectpartialtall.xpm new file mode 100644 index 0000000..652b96f --- /dev/null +++ b/resources/bitmaps/selection_selectpartialtall.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *selection_selectpartialtall[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #C00000", +/* pixels */ +" ", +" ...... ....... ", +" . . . . ", +" . . . . ", +" X X X . ", +" X X X . ", +" X X X . ", +" X X XXXXX.. ", +" X X ", +" X X XXXXX.. ", +" X X X . ", +" X X X . ", +" . . . . ", +" . . . . ", +" ...... ....... ", +" " +}; diff --git a/resources/bitmaps/selection_selecttouching.xpm b/resources/bitmaps/selection_selecttouching.xpm new file mode 100644 index 0000000..1326aa8 --- /dev/null +++ b/resources/bitmaps/selection_selecttouching.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *selection_selecttouching[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #C00000", +/* pixels */ +" ", +" XXXXXXXXXXXXXX ", +" X X ", +" X X ", +" XXXXXXXXXXXXXX ", +" X X ", +" .... X X .... ", +" . . X X . . ", +" . . X X . . ", +" . . X X . . ", +" . . X X . . ", +" . . X X . . ", +" . . X X . . ", +" . . X X . . ", +" .... XXXX .... ", +" " +}; diff --git a/resources/bitmaps/shadernotex.tga b/resources/bitmaps/shadernotex.tga new file mode 100644 index 0000000000000000000000000000000000000000..a666c7b93883b96b63795adfa107dbc2eb17580c GIT binary patch literal 4494 zcma*pYfO|^9>?*Sanx-Sk|yiYP2;u=Yj{CYlF~FW8Y8hm!=|p;DyA_sH6{|l8(t_E zff;TB3Q7@gs5|wFt_8eu5g8R!s3NwA+NBGNw-qZ=txezl_I%G|aNHMN=hZXkdCqhG z=bZofKOcwVbw}_Q>xgx{`#Yc4;g!#{=~b^P>ESGx`=yGaJSyg&J2!g0D*l!y{y4(I zG(G-(74uT}`*Y_iD#{`L1n*Svr^0s3%&XBk|8;e@_h?}Fwhm;cDUUmEu4(&D?RCqP zwV+D1Lr-+A^{l!&dUbu|nQAu{YFS;KS|2^q)~(YuufAS)?i46I+zc{xs_Gv%tE$TM z^ZVs*Z`VJEdz72|>1dy-s^4q#Xb=u>xHmB9gq2NKbh9(v&chp)r;;D;sdhuEJP`%j z^Z2P6s@K?i>$>}N=BuT4R*BcHY46@}IVMh2*@Fl2dOy;VrY7a&{K*6~PI-16rkR$` z&V-kG*bu&Cwi9wv|E}YQ{5o)>UpG6xu!DFa3$=fwTU+L5DLcMgZO{H;m)skkuZqEk zI*_$ij_`05-nnCD=eDnoJa|x#AD`6f)qe;KJT59VRof07Qq_$c>gbqfLVD4|2C$g` z-eI<0vk6@-1uBjyR^hEdo!Y+A29lAyOMN5zRT$~f8na;!|Gi21X3g03+NZ7B5St@M zbhHjXYtoDvW(%fGQ)ye9ZjM}5Vxr6L`$Dq~EiDTb6BDDAw{B_s_CLPVCnS`d*bQp@ zwOJcy()0Jfe@EeYjCJq1rZ*(Q&QAF zRH>;Z6E=TyQV9tY^q2GJ&G4}bi;dOkUmH!BWAx|pa`p7AwOJGF6A|GE$&9x>zB6V; zF3Mk1Zf0Mo+^^eAQf^df`aX4@I-<&ib;>Xy9w`juOvd7!y3}x5r`Sp}%l`gqm6n#O zwsxr<#4a)(k`x*Fnm#b$1pB0$ zN~8bmF|rT%X#ediyUXr%IS19^@78tyIkUUPW*NQOz!3Hgo46Xg)?1^YJ2k3WkgePq z8@2t4-=^LFK9_#wwptG?m1lB^ia*S?&+@)f-s1AiDmNj2ps+AUAQ4?}&6+jZvV(xj zd2o_vV!kTx|ETZU&g-H%R{!~>Pe%&YD0BH9H9UD{mUgw>;gx5u>T2^Uc{2B_Y51v* zmaGydp}6aNGo7Q_ZR$aHcb+05BDA8bOMd^>(Y96D**ZQvtdf#(wmz)7eqEO@e_~I} zl*UG#J^PWpo_^+x8XKpm>_vFF9~SG_^Dga7%CuonmTp{1Q$bXb4*zsXn`dX+_PpK! zv!ctzp*=LxWiul?u1x2i*Q;c5u6_No>=eBb7pIozM>TnJpeE8585yasMmkKQI_+}_ zot-8t-nG|@dwR5M*BkaaxwqGpyVpa*`@+7(^x1Zb zSM|_+?OC3#ti-MAc@j9vcJ<788X7#V^z=z~2^5PVkj?#^ zfdS>^nUypJul&J%rKXON!>oG6!vUqFxI@ERR^6b1UawsthAaEmeYuPhL$fcQKdOOUAz@+ffy)7o} zB5g5M#b?s|(cKDD@ba~+{-Vj#z$bvCzCSa~=3vp_pjNCHBlqIP%FKMtu2P_Fyxue4 zrna`(fzVB1Z2aYgv`hHrC{xS=ogrPN=ojt2mRV$qfx^Zq8Cldb%1Kc4)x@S77ya z5{0wSbI5L50sq#sHO`qHk6rI={M$jh%YHiVJ+fab`(zRKTqI28y}QcF8Y}y>kEeay z`#)E&@W{)rvUSo8Nthtr0SPb(5G8nrULc0gnq`d>-~b&<9{YuQ6FR^S_yH-P1;l_F zkOO)^5GVpkpb10?_JJ-C2FgGhXajMe4zS zBM=G%fg+HEK!ZIfAH#SuEK#cn4?z4&VVMKn1t} z8DN9G1|J{u-jk;)PNh119m_UA%Gw-1d6~BNMeQT2Q?51XaF1_f_(=S z-~wcT4bTBTKnNHCCEz632eg2f3{!3x64ZbjkOOu=5BLE=UCZ~z@Z z0#E=8fB`fB2k-zOK!i|%2|x+<0WJWB>P9HQ2H*f4zyo{$5D)@Hzz84#C7T`gusOjB zXbFHN_|L~1U*(98K4;D0rX-^k)Pi16jGeQ6mwjd|S%g#&3vxj)NXDARa>E5hAPEq$ zDLUE$RxDe_G8C@>G{6Sn03G`pTbd^U1fW1p0|x-XHJ|_%oPMAIIDiKz0VjY2m;jVu z8*l+IoC81uYyb|>0X%^%SZyH$h=3750!n}hH~}b2sX%xD0Vog>Z~zco0}5clHP8SY zzypBL21tMjKnb=17XSlf$N*r&(F8hxhwuOp5CTNNh$2QiU;<76DkMCB02HYIzyUx| zpa2E1pgI5zzyUl64V(ZHV8Z=i8*nK$XM@$_*a91X19SinMUArsga8pR;*(E!v;!vq z6%rmm01AKsH~#y;gj zB*=sewN|lDy7*-6F^(artZaNl>SNg7zA}&5-@H!};^xQCji2+^_dcB&|8ZQj c #670000", +", c #690000", +"< c #740000", +"1 c #7F0000", +"2 c #7F0100", +"3 c #7F0001", +"4 c #7F0101", +"5 c #171B42", +"6 c #1E235A", +"7 c #1E245B", +"8 c #232968", +"9 c #232969", +"0 c #242A6B", +"q c #242A6C", +"w c #262D72", +"e c #272D74", +"r c #2E3477", +"t c #29307B", +"y c #29317C", +"u c #800100", +"i c #800001", +"p c #800101", +"a c #820202", +"s c #8C0C0C", +"d c #8D0E0C", +"f c #8D0C0E", +"g c #8D0E0E", +"h c #981919", +"j c #991919", +"k c #981A19", +"l c #991A19", +"z c #98191A", +"x c #99191A", +"c c #981A1A", +"v c #991A1A", +"b c #9F2420", +"n c #9F2421", +"m c #A12420", +"M c #A12421", +"N c #A42424", +"B c #AC302D", +"V c #AD312E", +"C c #B03131", +"Z c #B13131", +"A c #B33331", +"S c #B33133", +"D c #B13333", +"F c #B33333", +"G c #B73C38", +"H c #B93D3A", +"J c #C44846", +"K c #D05452", +"L c #DC5F5C", +"P c #DD5F5C", +"I c #DC5F5D", +"U c #DC615C", +"Y c #DD615C", +"T c #DE625E", +"R c #E76B69", +"E c #EA6D6A", +"W c #F47876", +"Q c #F47976", +"! c #F47877", +"~ c #F67876", +"^ c #2C3485", +"/ c #2D3586", +"( c #2E3689", +") c #2E368B", +"_ c #323983", +"` c #30388F", +"' c #313A93", +"] c #323A95", +"[ c #323B96", +"{ c #383F91", +"} c #333D9A", +"| c #343D9C", +" . c #3E4492", +".. c #4C52A5", +"X. c #565CB1", +"o. c #6B70B7", +"O. c #7B7FBA", +"+. c #4550C1", +"@. c #4651C1", +"#. c #4E58C4", +"$. c #4F59C4", +"%. c #535DC6", +"&. c #606ACA", +"*. c #7980CB", +"=. c #7F86D4", +"-. c #FF8482", +";. c #FF8582", +":. c #FF8584", +">. c #878DD0", +",. c #8189D5", +"<. c #838BD6", +"1. c #868DD7", +"2. c #8990D8", +"3. c #8A91D8", +"4. c #8B92D8", +"5. c #9198DA", +"6. c #9399DB", +"7. c #989EDD", +"8. c #F3F3F3", +/* pixels */ +" ", +" ", +" D * ", +" D :.N W B - m * ", +" D -.N W k E g T a K < J , H ; B = b * ", +" r D -.N W j E g T a K < J , H ; C = m * ", +" .O D :.N W j E g T a K < J , H : B = b * ", +" O & _ S -.N W j E f T a K < J , H ; C = m * ", +" ..X 7 q Z -.N W k E g T 1 K < J , H : B = m * ", +" @ + e y { Z -.N ! k E f T 1 K < J , H : B = b * ", +" X.X 6 9 ' } D -.N ~ k E f T a K < J , H ; B = b * ", +" $ O ^ w ) $.*. Z -.N W x E g T 1 K < J , H : B = b * ", +" o.O & 9 ] | %.2. D -.N W k E g T 1 K < J , H ; B = b * ", +" 5 X ^ w ` +.=.=.1. D -.N W k E g T 1 K < J , H ; B = m * ", +" O.O & 9 t ) #.=.2.6. C -.N W k E g L 1 K < J , H ; B = m * ", +" # X 6 9 ` X.&.5.2.7. C -.N W k E g L a K < J , H ; C = b * ", +" + + e y ^ $.,.2.6.7. D -.N W k E g L a K < J , H ; B = b * ", +" q ] | &.6.2. C -.N W x E f L 1 K < J , H ; B = b * ", +" C -.N W k E s P a K < J , H ; B = m * ", +" C -.N W k E s P 1 K < J , G ; B = b * ", +" C -.N W k E s L 1 K < J , G ; B = m * ", +" C -.N W k E s T 1 K < J , G ; B = m * ", +" D -.N W h R s L 1 K < J , G ; B = m * ", +" C -.N W h R s U 1 K < J , G : B = b * ", +" -.N W k R s L 1 K < J > G : B = m ", +" k R s L a K < J > G : ", +" ", +" . . . ", +" 8. 8. 8. ", +" ", +" ", +" " +}; diff --git a/resources/bitmaps/side_resize.xpm b/resources/bitmaps/side_resize.xpm new file mode 100644 index 0000000..354f6f5 --- /dev/null +++ b/resources/bitmaps/side_resize.xpm @@ -0,0 +1,43 @@ +/* XPM */ +static char *side_resize[] = { +/* columns rows colors chars-per-pixel */ +"40 32 5 1 ", +" c None", +". c black", +"X c #3E3E3E", +"o c gray43", +"O c #F8F8F8", +/* pixels */ +" ", +" ", +" ... ... ... ", +" . ", +" . . ", +" . . ", +" . ", +" . ", +" . . ", +" . . ", +" . ", +" . OOOOOOO ", +" . . OOOOOOOOOOOOOOOo ", +" . . XOOOOOOOOOOOOOOOOoo ", +" . XXXXXXOOOOOOOOOoooo ", +" ... ... ... XXXXXXXXXXOOOOooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXoooo ", +" XXXXXXXXXXooo ", +" XXXXXXXoo ", +" XXXXo ", +" X ", +" ", +" " +}; diff --git a/resources/bitmaps/side_rotate.xpm b/resources/bitmaps/side_rotate.xpm new file mode 100644 index 0000000..ab8d223 --- /dev/null +++ b/resources/bitmaps/side_rotate.xpm @@ -0,0 +1,44 @@ +/* XPM */ +static char *side_rotate[] = { +/* columns rows colors chars-per-pixel */ +"40 32 6 1 ", +" c None", +". c black", +"X c #3E3E3E", +"o c gray43", +"O c blue", +"+ c #F8F8F8", +/* pixels */ +" ", +" ", +" ", +" OOOO ", +" OO OO ", +" O O ", +" O ........ O ", +" O . . O ", +" O . . O ", +" O . .OOOOO ", +" O . . OOO ", +" O . . O +++++++ ", +" O . . +++++++++++++++o ", +" O ........ X++++++++++++++++oo ", +" O XXXXXX+++++++++oooo ", +" OO XXXXXXXXXX++++ooooo ", +" OO XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXoooo ", +" XXXXXXXXXXooo ", +" XXXXXXXoo ", +" XXXXo ", +" X ", +" ", +" " +}; diff --git a/resources/bitmaps/side_scale.xpm b/resources/bitmaps/side_scale.xpm new file mode 100644 index 0000000..0362663 --- /dev/null +++ b/resources/bitmaps/side_scale.xpm @@ -0,0 +1,44 @@ +/* XPM */ +static char *side_scale[] = { +/* columns rows colors chars-per-pixel */ +"40 32 6 1 ", +" c None", +". c black", +"X c #3E3E3E", +"o c gray43", +"O c blue", +"+ c #F8F8F8", +/* pixels */ +" ", +" ", +" ", +" OOO OOO OOO ", +" ", +" O OOOOO O ", +" O OOOO O ", +" O OOO O ", +" O OO ", +" ........ O O ", +" . . O ", +" . . O +++++++ ", +" . . +++++++++++++++o ", +" . . O++++++++++++++++oo ", +" . . OXXXXX+++++++++oooo ", +" . . OXXXXXXXXX++++ooooo ", +" ........ OOO XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXoooo ", +" XXXXXXXXXXooo ", +" XXXXXXXoo ", +" XXXXo ", +" X ", +" ", +" " +}; diff --git a/resources/bitmaps/side_surface.xpm b/resources/bitmaps/side_surface.xpm new file mode 100644 index 0000000..c5849fa --- /dev/null +++ b/resources/bitmaps/side_surface.xpm @@ -0,0 +1,48 @@ +/* XPM */ +static char *side_surface[] = { +/* columns rows colors chars-per-pixel */ +"40 32 10 1 ", +" c None", +". c #0C0C0C", +"X c #800000", +"o c red", +"O c #FF0100", +"+ c #FF0001", +"@ c #008000", +"# c yellow", +"$ c #C3C3C3", +"% c #F3F3F3", +/* pixels */ +" ", +" ", +" $$$$ ", +" $oooo$$$ ", +" $o$$$$ooo$ ", +" $o$ $$o$ ", +" $oo$ $o$ ", +" $oo$ $o$ ", +" $oo$ $o$ ", +" $oo$$$$ $ ", +" $oooooo$$ ", +" $ooooooo$ ####### ", +" $$$ooooo$ ###############@ ", +" $$$ooo$ X################@@ ", +" $ $oo$ XXXXXX#########@@@@ ", +" $o$ $oo$ XXXXXXXXXX####@@@@@ ", +" $o$ $oo$ XXXXXXXXXXXXX@@@@@ ", +" $oo$ $oo$ XXXXXXXXXXXXX@@@@@ ", +" $oo$$ $$o$ XXXXXXXXXXXXX@@@@@ ", +" $o$oo$$$oo$ XXXXXXXXXXXX@@@@@ ", +" $ $$ooo$$ XXXXXXXXXXXX@@@@@ ", +" $$$ XXXXXXXXXXXX@@@@@ ", +" XXXXXXXXXXXX@@@@@ ", +" XXXXXXXXXXXX@@@@@ ", +" XXXXXXXXXXXX@@@@@ ", +" XXXXXXXXXXX@@@@ ", +" XXXXXXXXXX@@@ ", +" . . . XXXXXXX@@ ", +" % % % XXXX@ ", +" X ", +" ", +" " +}; diff --git a/resources/bitmaps/side_textures.xpm b/resources/bitmaps/side_textures.xpm new file mode 100644 index 0000000..25c2197 --- /dev/null +++ b/resources/bitmaps/side_textures.xpm @@ -0,0 +1,148 @@ +/* XPM */ +static char *side_textures[] = { +/* columns rows colors chars-per-pixel */ +"40 32 110 2 ", +" c None", +". c #0C0C0C", +"X c #240C04", +"o c #291207", +"O c #3C1D09", +"+ c #381D14", +"@ c #372116", +"# c #3E271F", +"$ c #452B21", +"% c #4E372F", +"& c #533C33", +"* c #5A3F34", +"= c #5B402B", +"- c #62463A", +"; c #63483D", +": c #6F543F", +"> c #634B44", +", c #694F42", +"< c #6A5248", +"1 c #725647", +"2 c #72574B", +"3 c #775D50", +"4 c #7C6251", +"5 c #FF0000", +"6 c #9E5113", +"7 c #AC4F0C", +"8 c #A5540F", +"9 c #B2530F", +"0 c #A05116", +"q c #AD5514", +"w c #AC5B1D", +"e c #B45514", +"r c #B35A15", +"t c #B95914", +"y c #B45C1B", +"u c #BB5E1B", +"i c #B36317", +"p c #B6641F", +"a c #BB631C", +"s c #BB6B1C", +"d c #AD5F22", +"f c #B55F20", +"g c #B85D22", +"h c #B46425", +"j c #BB6621", +"k c #BB6A22", +"l c #B76729", +"z c #B46C2B", +"x c #BC6E2B", +"c c #BF702B", +"v c #BC6932", +"b c #BE7130", +"n c #C16A1E", +"m c #C26724", +"M c #C26C25", +"N c #C36C29", +"B c #C96D28", +"V c #C77326", +"C c #C4732B", +"Z c #C9732C", +"A c #CA792E", +"S c #C37432", +"D c #CB7431", +"F c #C77C33", +"G c #C97A33", +"H c #C27738", +"J c #CC7D3B", +"K c #D27737", +"L c #D17A3E", +"P c #856856", +"I c #8E7362", +"U c #927565", +"Y c #CC7E42", +"T c #CE823B", +"R c #D1853E", +"E c #A28975", +"W c #A3877B", +"Q c #AE917F", +"! c #CE8342", +"~ c #D78941", +"^ c #D18E4D", +"/ c #DD9555", +"( c #637BB5", +") c #6681B5", +"_ c #6A82B7", +"` c #6781B9", +"' c #6C85BB", +"] c #6F88BD", +"[ c #728CBD", +"{ c #728CC1", +"} c #788EC0", +"| c #7791C4", +" . c #7B94C3", +".. c #7F9AC5", +"X. c #7C95C9", +"o. c #7F9AC8", +"O. c #B29681", +"+. c #B29583", +"@. c #B59888", +"#. c #B99C8A", +"$. c #BAA393", +"%. c #C8AD98", +"&. c #839ECE", +"*. c #84A0C9", +"=. c #88A4C8", +"-. c #87A2D1", +";. c #8FA9D6", +":. c #9CB5DD", +">. c #C3C3C3", +",. c #F3F3F3", +/* pixels */ +" ", +" ", +" ", +" >.>.>.>.>.>.>.>.>.>.>.>.>.>. ", +" >.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 >. >.5 >. ", +" >. >.5 5 >. >. ", +" >.5 5 >. ", +" >.5 5 >. O +.1 W - + $ ", +" >.5 5 >. E : O.#.@.U < * > X & X 3 4 * ' ", +" >.5 5 >. k o $.I %.Q I U - > $ * # % o ; I ' [ ", +" >.5 5 >. C F x x k v P 1 2 - < > % X @ ' _ ) ' ", +" >.5 5 >. C F F k h S 6 x J q & & * X { ] ] | [ ", +" >.5 5 >. h G F j r S w w Y e u p N { } X.| . ", +" >.5 5 >. 8 T C D a S c d S q N N j [ [ X.&.&. ", +" >.>.>.5 5 >.>.>. p R S Z N G c b z q j j k { { ' ' ' ", +" >.5 5 5 5 5 5 5 5 >. R R j B J z / ! S N M x | [ ( ( ( ", +" >.>.>.>.>.>.>.>. G C C a x z ^ / g N D G &.;.&.[ ' ", +" ! x j d l h J L w n m V _ [ .:.' ", +" R m m N Y d x S g t s C ( ( X. .( ", +" F N 7 C v 6 Y D e u B M ( ( ` ( ( ", +" F M 9 m h f K B r u B V ( ( ( ( ( ", +" n a r u e r a s B k C ' ( ( _ ", +" a i s n 9 n s A A s | ' [ ", +" . . . n s s s s A V ..-. ", +" ,. ,. ,. V s G C =. ", +" C ", +" ", +" " +}; diff --git a/resources/bitmaps/side_transform.xpm b/resources/bitmaps/side_transform.xpm new file mode 100644 index 0000000..b7e6f85 --- /dev/null +++ b/resources/bitmaps/side_transform.xpm @@ -0,0 +1,44 @@ +/* XPM */ +static char *side_transform[] = { +/* columns rows colors chars-per-pixel */ +"40 32 6 1 ", +" c None", +". c black", +"X c #3E3E3E", +"o c gray43", +"O c blue", +"+ c #F8F8F8", +/* pixels */ +" ", +" ", +" ", +" ....... ", +" . . ", +" . . ", +" . . O ", +" . . OO ", +" . . OOO ", +" . .OOOOOOO ", +" . . OOO ", +" . . OO +++++++ ", +" . . O +++++++++++++++o ", +" . . X++++++++++++++++oo ", +" . . XXXXXX+++++++++oooo ", +" . . XXXXXXXXXX++++ooooo ", +" ....... XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXXooooo ", +" XXXXXXXXXXXoooo ", +" XXXXXXXXXXooo ", +" XXXXXXXoo ", +" XXXXo ", +" X ", +" ", +" " +}; diff --git a/resources/bitmaps/side_vertices.xpm b/resources/bitmaps/side_vertices.xpm new file mode 100644 index 0000000..4aeb50e --- /dev/null +++ b/resources/bitmaps/side_vertices.xpm @@ -0,0 +1,44 @@ +/* XPM */ +static char *side_vertices[] = { +/* columns rows colors chars-per-pixel */ +"40 32 6 1 ", +" c None", +". c black", +"X c #800000", +"o c red", +"O c green", +"+ c #808080", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" OOO ", +" OOO+.+.+.+.OOO ", +" .+OOO +OOO ", +" OOO.+ +.OOO ", +" OOO+.+.+.+ XXX.+ . ", +" OOO .+.XXX+ + ", +" +. XXX. . ", +" ++ +XoX.+ ", +" . . XoX. ", +" + + XooXX. ", +" . . XoooX. ", +" ++ + XooooXX. ", +" .+ . XooooXX. ", +" OOO + XoooooX.. ", +" OOO . OXoooX. ", +" OOO.+ + +.XoooX. ", +" +.+.+ . +. XXoXX. ", +" +.+.+OOO. X XoXX. ", +" +.OOO XoXX. ", +" OOO XoXX. ", +" XoXX. ", +" XXXX.. ", +" XX... ", +" X... ", +" . ", +" ", +" " +}; diff --git a/resources/bitmaps/splash.xcf b/resources/bitmaps/splash.xcf new file mode 100644 index 0000000000000000000000000000000000000000..e04de0242cd1c3f775ab5a025697f0189d6c38c9 GIT binary patch literal 391211 zcmb@v2Uu0d*2lf~>~rXvSdy5g2vSspfUH}_j$kXdA=lvGkedP+2!n6GxMAOUitXb zjb7G%>p!yI<~C;z$8r2}{7J=!{yB-y1blcNe?nn|!g&Q6&@qx@Ci>?}iVpw}0yUiS^%Kf9qY!c~~<2bff3T z8=ODh^67`PkE~-HeSH1D*kJ9m`HN3KoN3|s(Z}mIf3bOswWIXqyyfF{p4M*CzjJ0H zd)>o%QlYcg*bDsRbb!^<=>-nLluP5!tweW`)EcV;@h4r=#TYNskXUM zZ~hRcw9w)G<$rUY=dgH@#e#bU7g<Sgrk6d1-TjcV}r@lV# zZrHNSbEDT68*CQMv&O#*Tw(gZ7m*6Sxna}CzMt4EnltbJmH7vsen{5OJnQya>D1#A{Q_xxX6=l)+uSn>a%#Jitt_TB9AS9(yE;RnYJnlpAj z3ta!WxAe0-@Q)zwlNNWyx!$ z??3&y-=y#uaOR$0czxn&Z9A7tsB&sT^YEEY=|!G@KaX!SO5-CH7)#@evw0x>)B|aY z2h!FLq%9vvTRo5-_dxm~r0IB^h=1^9iVq#z8Xr4KW48g{bitG+8`}r=Kj;Rq``$mY zb@x)ZjC($X>cu}Y@+ay|MtJnDLL+6Kod_Ele_8t5FYV7;?*!LOZH^&7({QvVu zj`|+_$i{K{32$vT-=^RG)&wS{&7`zkN}FsGw_9?CiN51}w7wI#iN01o2H%MS?J@VU zUSPEeC+t~WBURF0s<l7Y^w61?_x@Gw^5F&zfI;Jrf?e#q|Ex>EF&+sW_fpeqqtQO;mfJFhA$& z)r^#=v*A;5-}us;oa}qs)||>AKP4$KG2!>Kzn$5PJK&q0k(xBNY0ZuD7os8~Bf?L! zU&ON}+^BU%Y!o(sqs{4@+AI9m?~fll!j6gGnsRENKemuJ_4JXj(9jSTDjr7O16wwn zcti*hLd7FyNXf|Jhz&QqI^^Jieftj_3|(V3wy}>kkTd1r?p?vbyLN|6LC%zedv@)T zHf%X@mpoA1ZNZ7V>3aqzF2naSY)juWIK$*1A{>P%ZONDjW+7a# zpx`WV%Pn|w&WyL>^mcoT%nS8Jd+p$MkT#Rja63qw6pBS?zIJ)zazu zZpSXqJO|F;8fU?3L#%8VEH)UethW*ic6VdxTK&UKfho*PrFpurBIX>)1LkC z7kepDTq^BADSJEzeT9nhii*k#ML|Jf(R7Y>sc2VaMOkq{L6JR5*%u*?>M1NJz@4$* zk2X~)T<5EFwH4c^Gigt0S*85CzG}ZcoQ(ZmrBbFalq$w!TrA#})o9UZPNG%Pmb^xP zL6&F>~Nwlh7SUB_-}>Tcb>a|-ud;3skVSvPOVZtL$f zIa3R5VR!i3?2e(u*a~I!e1pF6dmO;Px0==)nw`l3?PU$JMni|Rx1BYL4Xjbu)chlg zhz=%A`sNN7vPTmZn)qfz*K}%Pkq2CQn%=w4*ZsP_D@xS#7J=)&B{+LWYvn5akmecq0Jr`@nUtp85p0Mb){cy7qNy%k zFQW~0G@aX=;St%WUM(U+%SVpTmO+&264?k-=?BS68Rm`(L%g&hUG9jgudB1GtNXN6 zol5A^7`IU0+~ICnm%dxYsJ`#v$^>bP&SHRduy$Qr^9eXpgG>AUEo-=sUpm@a$$!<| zteZ{lq`$58{YgFxdW)p3V(BmK7Bh!bz4nnsIu^vSL}0q0G?+btvQo1_f`1lu}B;$LyCE zi2LDFq;L2X`y!#hP-Kq=J(hw`p$4<>_W#UrPWo)VbIqv!K z%dm}o7R!+H0ej!De3|L{Y?=Na+&{;^FMfcme_Eh6(OtGeTqUg5yRTZo{;7B8+=Uf} zmF~i7gZoOhLUf0n2U{_^a@Fe9t5&X9fdUJJRekOsZQQ(h)5jjGR;^t147b3Yts3>9 zT^m30V5?xki7#N6`-h)@{-OJ-RXArW59VI)>9u`FVBn5T9`5cQ=A6}P4-a?G&D$}M z+4d3kSi!kiA#2TNn>Ky6#@!va$ZElwHQvE&w|dW)Ux%)L)*NFPtA%UVc<czDih0XenYt}r>iJ@%2=D@*2$4-2R?!vec688O57{NUJhL6u14^QNVJr0Iq z&~)j~)8%2lk`xr=KIIq0>C`^Upv1_#^vSJY&m=(ZX46 zc*MCV9}HfiSU7){57$S;TBFdqh;xxFijOv2#A73#XHja5jbdWYV?Z?YL}W~C?D-29 zFQ?8x-X#_@8hhcw#fz8Hrl6nNb>Y(Q2?@z*8GF!=ZjFhF{ryTpLQ-nhIuvyHJq=@= ztgBb^$+b+2jX9r~nwfbuJ13V~FjF#gE3DX1J`+W!ZB0fGUR+#KT8UwSfe&FNe5r9D zKt7?uxLIBqilSNvC7e5})L*}ealueXDLHnGd~QjOp#e?pmhSf5PFLhPl-f!!Ld!`?oLlhjElP%ee&>;$I0PjBqk)> z+s621MoM(#xpNVx4~HFEi^88@xfFA66QhvtPo6w+^hhW>C?3M_<>`yxeT&U7+Qj*; zI~wxUjsQQ5%Y$%E8~)fr!N)`We0{cfvn}FQ3|}4(+3vH&xP{@%R>51?B5uROwpB(J z+wkKr@%GxZanok6EpK9wf(+6o@WYLVyKg;vy@7Oq7? z8pX)o#?!&f1p|ZQMaqm)H1206g2-UYo7p)#Im=yi&UR)zToJaJv*5xz>#>b?NBJ@r zgR>oMnO$UlMv+jaQIr<^WWz89z@Eaq5}{06nt#rY$+;mmBfLmaR;ExCM_O@x%yER8E)Xn*u7-@ z4BR(5zRNT-+HbZPcaze#u)z%|U$);?y0;EvY3!$ZYPZJB7GQrrjTVD?RJZe;=~ug# z#9q`gIPkml1K&$x@7TG}jl7pc^AcQ3N?Zjr17pHodio>ns1czVAb~x2R`G}$n-vlc z#>Z>C@hg~>pzG&2!&?&;a(W-%i3>O$3<7^a8y0YSKmS1d^$&Ote=*Zvz!{cK`a4|h z(_ZN*LaIK{h8|9vz%K%zsOiE5k z%gTL5#)+9MS(c>=bMlMJYW~8w zA=}jCWOT2X>W-BjO~0B;nX?#o2bq{$eSPD4SxG@&c2@dqnrltfduuRaF1*>4-jakJ}g)Kph0i}G@v<>~{i&Z-T!u{FqZ{K>kTwKY}OD~so%)8S8|P<2gBZL>3j?Jaq= z;TG(4{H>ZAp;}*K+~D{oc1u%NTT^|42B))X+0Eg)JB=+r(s|d*>kReMk?Msy{-(Z; zHncqF*4M(Cbd3$Qv&>O5ra{9^%`Gk6RHQ@RB(}?V?V{e6rY3>rPy&Xl+B3b~T^RTq zdeH#U>ti?|v}<~)Krh22K)fYj=GfNV-PzXG(%k%mR7e~$={5}F;nX{~cFDRmxI&Is zu^xSQXRSPYxVWRcx4W~wwZ&ORlfy2P7DE?3$$Xc*MbkyLU93glRFlUrRv5``?CkF8 z>FQb_%_J*L6hJ*>Tq##*D#^H#DfA_IIV^7|2g3(NV|!0;{~{Br`MQbHa2>`v>%3Bh zpwwI^^Xp96t0*c`C>4b{IYs4)qWrx7L8k)qa#oB1SAn6Fifp@@ohK9+XWUAmsIRcJ zth`K-n}@D1=W0e8nuVI@fKhUqUQu3NR$f|=otc)B zyc6C0J*e`p$LZduvP3OWudA?6lP3-r*4#KW^Eu)!pk}Esmncj4PbUwrHwWQ3`rFy? zhznOzf?*yl4;P~uujhD5MIl>aE{6|=g%J%|8xnTp_=#_SOt6LRDfw~n6q?&9;W&TH za0vNBPoF*-78()~8u}sirBe?cMEcO-qaVTQh&&ARg)Hhn+{*&BL6fix@Fu~yJW#5J9jL@5}fOf9Wrjn`ruc= zMo#M|`1669pq;zEI%q9$n%$U=1O%`^9-on2+;W^yU{D|n(CnDRtq%wQ6PUV_1!;j{ z{rqGo$p;yNr{abLF@Mp|%zrqDn~YNmKt2o5zQgVG_s1CcDcS{VKi*%vBQWS6li>~D z1MBreC31hwdmQ&vP=G)sg1LA6{o#8j|6_+-veySJn{3LBPND-!hcpCC!fkufX#Ng| z40BFDX(_gOKfV9bN$~uW-V53hxC2+N4|;Esj2oP^6d0M1FxDX7v&8T5w_p}%czcpH zr?Gs|Z|8fHf|=ad-|V$Rf0L7Ehn@!TiT)aj`4>4pACLNroF0@2Jr1Z3dYt|C=A0=% zjvJ4Uh$cVCI~?q2#dN;yJ0~97dgt%kEn0PVw^>sobove#!8?D~)Ywqp!0NRPja>&Y z1<*NThrC{FkYQKzkIvNXI5jq4a_;;~6HtJmzCj@4rk20R;Z(i{3ou%1u&?wVL0$9FU~;_;i`g}DsM0#D!oe8V8BZf);cM6Tm| z_(i@+k31t;;+s^>t!*&kqzKcV~fygXc;R$ z3hYFAZj9?i9u9E4ucxb{9W15wF9NS}2x|dO20!j06TS1P9(jk@&DsasI@((ZLV+y* zhEvx#pW-;T0oJQQ>9+QHbWay_bax9q`rZiXnpN^1Q6=LIOl3m++1Tft?eFXB?dj?6 zT!5d4j{vXYdksT#=pMlyWSY91q26BbElQ*J;MD`HU)$GEH8NiRie^ZzGH6kZ=XKHQ(Gg+T zpuH56Jv1^pIx;*wq#BBl>_w4{vJw5Lfu6%T!=PQG8Uxvj!l+@S zPu@D*0Z4`-L&I~VZboCGGHA${*Dw_u(%}3!Udx8GD$HKngjRjqAnMSKjJ}MS63-j@ zyJVgEu6~rz^8H<%ovcgMPeuB>x;i`BTN#Rsb`B1!HQHA&PeS)GAawC#B@B)ovQ9M$ z!+wBu^>)&a)!EV7+R=@&9d8@^n||Ssjx`J|>|V#(C+iS#xFK|@ot^E}t%85z4*h~h zLufH{;tuKfo|b0Wz1~vRBDT`f!U%P$c;*O-;__TN&;oSzcdKTaV;0ql2bvm62c1-_ zykVq!P<7gEVMim{qM=Qi>T$&3G_}>0m4g@u-cK^^{5X;?#gS$yHc-%lo8Y6 ziIBh?Rv^k9Y2b3Ctci;i;YGdTiuGNs|993#$WWlAlI z&=AVNS4jv@N}=NplVB_kd=%C?Md=|23{IY*wH-l;!=XV;-(D$Ks;kTP+M`!Ti7I;j zCclje9Lt%qQBhJ{SWtMSv{b1C*CQmZR35?crxz6#0GacpYLM+Bd)_#tKz)xC6$;$Q zbLGm?;zDGZAmc`#Q<{8}VRz7;{JH(UQj-#W={|e(DE9k{jAzJk z_WLX_1ew0QtfWNowLLlmGdOWI|KumR%6;||LFZ@Si45lRj2+B#7U*D>F0(+_0vd_G z?~DJ$UtmT21t&!J0`7$hcBN5ggDiU zI+bZ#BF{yHM+o7%bC*7P5`D_la}g03?nKDKwGmOi)2L6G8WC>7jad6dV>wE))t-xr zj-e$)Z#HJDIR{R+)f$r+ukdg=H|%sFHU_IzlC_2y-UTPz&{!71qiPl|M2v(-NNA7t z>tiCqF{*G)ynG>+ju8%vQLKtapTC^wiqXLZ7A1?+oQsUadYBNUjlFO=(FJpu3sF%5 z*XELy5`XzT%Ar^|DvgYey&RvCH5Y?2A0bK;bN+lxB$%Rzk2?1J<@n?bWa)jPq8QdO z%W^YQ60cl79~~7PEsGjGe>ou~Gq?0v%weYC&c)I6>~|DY;Z&n9#wAgv1LJy-i3Ss_ zEXu|FC*k)C4ro`ZCq6~-w5+_M5(RAZ)4bE8K3>U+L|SyMz!ptk4p(AqjOLc0o8&sQqOlJM(TZ@sRmELW6-QCH$v#wt{mHFbCYMV{tHWhJZ7 zSKdJ7I@*v`7$~jcIoHm+w{PF7yIJNyodihc(5*Xn>szT%({0%;Lj%rJ!!PK(bL*D0 zdMnLfeuYN9oqJm^7sTHemJwaovLlP>m+q9&GI|?yUkR92M87Js#>T(3vp0I z62p*wB)P7oy%S@D7o@(Tz$DvH0CNLhAkWqmkZ}Ra?!B6lkerfCQ}3KxO&wi53#55T zp@4aiWUhZY>6&cJzL4egW@n^l=VYfRBxK}gXQm{*jE0X6HkW0J87xh_YRTy~#w8>R zNaCr>7BUR!*Dy`WPD;wYmX;WIF?JgK!S3|bq$@1WkciU}6WL`UPE3Mzk}R%2As;J; zd3o1T@gPS>`e1G`BP9l4HT?7*3`j?!BS6K$zAwW(Mt*J}wG?h-PD=E-h_h$UU|KRl zp!LYUo!j5T6lH>6=;0&BemooF3rBb2sBlDg^aM|j)ybo>BjRyb5P1#Qw$;~%nCM&H zzS{!=gTINGhRS!!1H_$ZZ#!iHdVgPJ>mCpF_myoiyh}5fN4>nfy|?&o_gfB=ZE_!R z8^(y+eB@iiH!*{GXA3g7cyIpbB`eHeCawMWi_Oeizh&D*GzE)iH_Nw-_;@aW2x#k; zErPdU%T|*f6xW-u1m`vu!*_~LWsp<8Xm zd6{e!z0i7Pt3B|XPVw>H_{rKe9v-V!tY9mLSFK*NcHJi%y|zHGB0kSNG^^LG_go8V zt6#`%5Y{ozQTIhS0c#I;_?ju3KVG+HjfXpyV1$*thi1*%^`C5>f*H(OETXJ(XC6F0 zJ!`qeYuBtF zSWjp7{?Mk#2Xn=o2;K{(Jq~S&SqLIOUYfV40JxuWoHcuvtr-Kp?IDQl?Cb*6?c(A* z^Dz@aYJ6s30=Kr2oT3V8_>G>IEM3M~* z>pO4?c7NGCeJWKw!{vf$3l@t{ykZ5?s5YH<{`_L1#%kVb@v)c5R3o^QmbwV& zumzXg#J^hLTGj07Z!%uDqLkop?jrV?{K-t&3kqtEIxPO4b>5M}t$t6eO&t_$6 zg)_)1`0=Gu@L?PtFtidWbQcdCbj*xMDIoPYvj4pU2l$hfYZ)Dr&f9WHmBNM0C{^Mt z+&2TL8gV6>`w z*M&Lrvqxvy*=!+;$;gQ_>a3M%+Bitcz@uw;AZDsv}dU9!C#{EPrromm&5J+ zpH;HLO$69h3yznH-jJ3+kSamy{*R9bk**m(G*w~o*Nc%e=`rBPy-|c`*TFo2v}@qf!zXQg2nt}=kRc2 z<<8_WOE+cJo8v8~(&7|7M!jXOcqT;%6uNoK`5_@M*-fyYHTCi7w%+u!ns9^cDX*8` ztLjTy@>+;37Mwxzphb2#c7_m_3`CE-gt>i?y(yyL{VI zI3QXu4V?wk$7eZBHy7||NQ-T7QMjADVDiW`Q@CaLD=k_v!%~KxXHW(F4T>WJ+~->_ zaN6;&Vx4h5DB4!RXCCBxJGO6SK7Zk~PXz4n-_CrV=SF6nJh6XAz;+*>=eXe~PMth) z;=m4nU)uQW)VJTBpuCTtWnqo<~%2HL<8V-7o(ye3>0G+ z$H$u>PYlHB=hzxTzj5UO3klL909l{=S573o+u zU`hJq6mOKlYJoZhtH#nA1D`aKoSK@6wdWECjL(y?jFO&lH7Bp+7A*>7XJ1Xm>O|^P z42h(50wG(Um7F9?9!g41&xG8tsQ3^bLDkySjH^%|<`-907nv06GP1m<;(lUbfE8m( zSdcDc@R`GDnb|l>siJxwN=&&1`BhPINqKd3EtBMk=%aM}&Fj|%S`LtLExcP!UXil$ zdi9N4v*7>^6c%ISdUZ|pEuosP!6E_K-zNBvg#Z(-ZEo@Ps_L2>B(K7I21R8gYQ%bh z5&DnE^JYxZ^LDZZl16sR(1ddzey#>)wUAta^3kW}0KUo=5WChqS()hfSIuYLaht3Y5eL#M)GN#DNI7>Ih;N0MH9iZU4 zly6K?{yo+&mO`XcDrFi|lnH=OLJBlVnJkaf6Ticm?9s0R@oX@S?a$2uU(f!CgyT$qogEO_nbt12P5%*)Anjx)@xW|dk+L4Iz| zvuK7VXl+oZC@j2efezy#d~~zxAW+5}IoW}418w9XNF)dkXXEr;n`Nu6bd zIyp_%OpdFsCoryo{HCE1VoPG~fM$!MC)N{#?o5OXRz$!-aFKz>;i* zBYYGw?7NbkF-3i4P$0POX4hp(b!k!Q<9uPMpcpO%kR~WyP#_el z5AYJ#78C;{*rxxW-v;tyN=>;V9w@Am50(N@7nM;laBK!h%@<(tk4h_f32!qHZF#{k z_7xVED3s*@*m_=BBrnvGgdA#gDk4IQt}o@w%nC#pi$tJ7$(4{`0AR~t@@PJ?6%+1( zZTxooO$xAcAHR=w>WdBl(HfPqg$0k~YYXi;`@)Bf`p zK&h=tbihk!iUP00X2ArsuJQaMIH@Fvp#>p^HJQ@CW=k(!^efOH8G6d8SiN@Zm3C7<&_?4>wJUsJP+OVWf)K1-W^{^G|^ z0+E71XUQZ4%`OZR`A*Banq7=%P4&nXyb+XzyC@W_IhVMzw${`hwqNGArs#nmMkubB2h_eaT>ylLn!K|{p@-UppjEr2cY*neE z6i6A1RN%|xs_(;Q@&Jdh)=z!B*fVq_trMH)v)btJZKW;#Sx`%82S5d-M-_ikYnpP&VQ@mYmflyR&d>| z7%q=n^x>j$I0Hr;Z-T_{z=8V;-lJZhdwR;b)n5o3wV!!-tXU&?h^tX@ zF(*-9?EM^Qca03-7igA&857s_5;nGfvTp6#)$Z=AJw4ZZ0uW0W7gUw4)vxEkecOrq zdSb5!GTm3fVC^Rxy?q|X()x#T8+ob6{}VP@D97f2sCu%DtEyH=oRpnft&K~ zI1`T{KwRXnS^tR_%~m|+t95I@V6A{zWh?a_oQDaBuQ}i`zsBJC2>iq%VKsO#aasX6 z^a0UbA>yNdfqPMoa^@2LT|5r{$7&6O0GXiigT|vEyelm^O9>X6uO#+s4i*1`b?5I_ux9h~ZiN{laVG;7$5K%2EbYH_EdA-AaPSQ3UYh5E59unlhl#s{f0TgpBQh z<_5se;;L2Jf47mKW@!nbX~+W|m&7Umn}v&_Y`zJm%e-J|@A{txAd$*Dnja{9p1U9Qoh#2y6Z?rt-}1n4OCcIYBV_j>=>w;u zF?*@_l08tU-t-AmbU^?18xppE@|&#JEio$BvT6R)jmVqW{{2cUP-=E!tfPQe*MwPT z@f%TM>=6KYynk5fr+(=;&@ve~U;E7tMTqJuTx4LbBVf;0mQ#VEV!U?o|VQ(q3bbKw$7C%Ni0b3*Vc5$Bdn5kg)w5_+a;?W>D z=T*kNW{IC{-rRSu*MIrJWvrE4dd>B8w6tV{WuK`N z(AJ;+`h@K)tp7O8c4jW3Gn;LEy@6&>62}F`MTXkhiI$7tPjE9B&zspgvWLjQ&@7i( zQ3~%Q;sQWlF!RskwA;6Be{13koZzGL_517izi=i#YTt)|6W*IVhcoq2ZwV%}GT9CL zb-oAp?_xpXGTIpZGUVW1yuSLS8;9jeUlyhfJ$T?im>Z|wzHOV=XMW$BV4V0or`>S! znH z0A^{tH>T;PCHFn_26vh*t&xKhSYKopMlM=&98}wcYWtG#MM-F{@iI@&T#QETvQvc= zed-=KLH*rbGjL;pN_^vetF=HyX~{Yc9V0pajoD7e${LU^GK2tH6} z-L0ducnp4wXqM$@L2C)lj-+K?1H3B=!|4q1#kslQxX?5BGmcgBRod#BhLf`~WCETQ zio1)73o_`f^7EwW#=uZfJxdN(RfZR(3-Nlaq6W{ptXe}ad*6D2Mp3nLqh4FbyA_lJ z*8sX6mL7&$c6}IxmR=>t6i%p7Q<1wZSgwa$n^ogA##@qIoj~ckQu>yxN?mPawla(y zwe?M{or?&z7-20vWA%77>n2|d`J zyhHbB)*(piqQ9 zyRF0?%EXSI-rnvG5~0;sVSo;}3&C1_zAR@bw=gHarK`KUtJB4tyUT-wbau4VDsuP! z($`Bu#FjcG1X^X7bJJv1P#8+`WqHE|rFq#U?OnKJ>Ywbs%FoNqt>|p5RTSpumoR?B z?d(Wbtx|gJT~kdZuM0{MuY!)0RVZ`vn!38_4%>byT;;R)tLl<69HS(co?gwoLUEg?kT*5tE1P7Dj@N$ zD2G;MuO0egEPbSi=_Z+)jH_2OGg9eUKqLb`$&i?ap$cidi-^D1+&DZF^Fc^5QcO}= ziazmTB;kOt@lYp|}e70|#KG zK0HOj#zF{>5^z{pfPeUn-SiqfwQPR1ETm)SdU&s;%XjYmX5WF3P*2$ZA>76r^l03Z zmV&Yxf}RBDnjW&B?bCy=3jRX@&|UA|6M`c=DA*R4q5YELnZ(;rV?)C{JjvgW=&~e% z<^y#>JA*%fg6BcCwzv!EY2P<~j}AaOYZQ=bmqBsu2V=s+=qn|WHZryJ zQs5-`g-HnL!Gk?RvyFy0$zPD{L97GbL$;Q5+JMNq9gt~bjDk(5wga(&)zL}sQu=K{ zV#&d59=efTz*`GA4af|hqWPSVqsuGZM&6NshLh)v=H^WY5ZP-#hcm%O9JGstMfPr- zX^tLBEeHtrqeIEj6)P1bc!bM%SCkQR1tp^u6jx4-YBS?Y>r5&kp+X)n9$|Qi-*6MH z`XSy1aJmX!jMa?ZLgN8fZe!5FwzkPyb)@%x7^j9em7lMv6>g5!^Bj0GiK0n$Ehqi< z&(74tIG^e|}>35hMn*}>7BbycuU17+tvXz{BX$h9df= zpQxu94Vy(h&9Kw)*5kkkCA^APh43ON{$NDt`s-e+Wq&y>MrjCOI+GIa~=mUVv_n5Pp zN&}5G#LU!5ND6H^uZM!0skC6nblF-r=*0&Fl@>7yxFOSFkx;T4szr%9iz=a8)w25z zX|9Kdh6WK50sZFB8bOLyaGn=nFNb8?5JNu@c(X7fLZ;2PiT!L;i_irz@QPagEK-nR z4|lqZuwen3Z;Lw4nt%o(ya5|&R`qta{S;}?X*6o} z5J71vzyW@GBF}iSscopev#qsrSh_@dQ^4iNZq}t892xBEWbI-f4m`hHP{_cByYq>G7nh-S2}m3D)*`(+4)rp?GbGKJwb)C1yT}wJ_(Bave;moI z5v2k5;cJ!;g?4_y*XSsaW|G2MT}WV^^wn4!rq-x5&xe+k9xCPdd>wr1Gw2nds}}d6 zH~88-UzCcE&(}jvjYc!3riSykFEk}xwY1L?%o<1<^mw9bmkGH=UNgE$~dwsTko#%&`NGu&$=8Lz}y*6*&;>KxE;k!%zVHzj~gLg0A zxf9WG_U!o@^N7j8!F%?y17a9B4LCM**9;0{J;1gNr+fOVBgfeB(Ni1;X*H>x^$;(c zK!Uw(8hQF@UYES4;k}?=WZ&z4;W#e_`V2GJ$ip=e0MmZfm`qGVordUH=n+2qk^M0I z<7Wt12$KlF@qWn|WdM*7GQo+a6x#(HJ(gsivt&fS_4kuj|V8fHMe(ICoK6whACP20h;TK{_SPkJcj`Dj#a{3HRDFPGX zE?+ng$@4T!DKPjis|azBrW9ikTMLLy^m{n^{G}_2DQOvB)6SQ|o5PJX*wz^aN~SuJf;$Vx`X$aV$A zxYE|%ZfLF!j1k!6G=UEe}btu3GT=QJV{S{SJ9kZzVKEDe>#C zK@E+UrtVhb-l!b*-=zgN#F03|vqnQxtGr!{m#Ti2+8Z!;cUz@V9+Z@Dy&dn;P!>%k zPBmeA)7jpbWy6{Vo6&Q&1A*VZRg0+wE~oh}g8$T}$P$N=(i2neVp`MI;tc7Ma|@<0 zcgwPqf*>7XUBjLAmGmNLZvH-M-UUK7;C9Mz+O?GUtIcig-MC2zS`meW3D?tut>rmcIXP@B2oqG-5Q*fBIg_K}Nf@4x5pS0Rb&B2snoM-G5Tl8X`sql> zLCh(3Y{whtK|#A=7(@zd%qXVC2+^2Ui;e`<81Y#=?@3`O=-ijcygOFE4z2HkMQcC% zf`VYY4RiRkdm?HCu)rdh-K;a%Jw3=|ZY^AswB|HQOruh{nD|L`rBO)JZrtcfS zNJk*eGYUx6%bRCs1v&+dF1qA^lM~s*2LU=HsAF!)TUD|cnrqFIW+cZXaq&IzG(T=h z2`Zq79<$Hy$lv!QE^Fk#ORl8nWzzD&J<^Vd5*2?Zjq<24D|}T+7A;N;w^x-b;+DAD zli2t^ktG1#eZ~fh0~Gt?^*B;d<7v|}omzE6`MV@x{*!Uvc{?)3WlmgD3Ra9C@`g&L z(+;Fyk-+T1id_|zM)59_xfB$+0;vuziBpu{Q0a7%@qH3ZFLD{h-BzHkHouYPm*Xt$ zo!tQBZ>R=RB&9B$g~CNHgNlUXWp_E8K2osvyfuKSxQ$4 z??mei_ci0nu9SSCbhI%3pdFE2dT|DNdX0%2h2)^6j&X0^7mh2c-g6abk%w3=y}gOk z1l=8Vc65tV;MjE2TuZ>vB|SCi$K}RVB(0g1fHNrJE}uk|s;XBm@nSED5_$ z!t?#~H<5YhrSoDgC=_p4!iJv`kl^%(&16nzW;+`}bJWhWU6v{?VXTX4E`e0vuVylx zznP$w&Y!ZK&i(xX4gEA6g#`Ba#d!0DVRpE?W6uO~ppjQxrr&b<&IPm}^M|7PA!F;e zfIi(-QHJ?1o>H*;h5KgUggW8&IpNqM*le3d?;SD#-gnl9nF6pU5iiDe3 zv@rf5`LsV5Y=RfyYywH*O#cNUC@^iw0G|VG@Fp1~&%kYilwq_=3$!ui+K^6>z&WJK z_z$mM*$_rgFp1YBQWZ(eD125Y&ZXS|;)W!OA!vh_x(wV77B7eL*$@D%E+&}9>-@1+ zO%J`l+$SfIC}z#OfyMankK||aal=8 z*oG(>BbB6xF~lw=v3NBd1jeA}i1~7rNxEolN@~VOa3!7o@u-H|L=;|*;tY(0EZ)F~X}PCVnX-vlI<97uO0Qz-YF?p^~|&sVT`>*RCNHS4K+OBcz5#)D{6M z1KdZAOA66_fJ+xzH5g7!zj_VOUW%b|m+~-o_^dc5mts&VOA+)O3lNzUqqU^8dKN~d zwtLc8YIm9|GJ-SHbMp%zltvI0ijhKj@SFAp<(0-Ttu)3&q!bOQosZ$6)F8}Fc91P~ zH~U$^C?wP1sdX=q8I~lZ7_J&4VyEs75nVM{I=FEMoyj0Yv#dZpH|l`tsm&s&>y3ND z=%KL?uea{ByG(%XUc_Iyar4$)5PHlvhAGG^*cEs>LetEug}AZ_G0}cOe>TX^LM?Y2 zDMs39X(-PL>}-yYpRqQNp_G+63Wr9iPn%_nmMPxSWobayn>l;j>D4-NlX zi6NmeR4i7UrO2@mnC6!tyw%`y1KrIMH7}gY*)-bb#1#|E|GD&N4UX7 zZP$UxiwknDx$Q-*2D@BLr}NTrgNOLz<;s$R-0Z7+sY==^Ey~Y9fS+i@${6O0vN0x3 zPf5b7dP6hIbFL8uKj%4RGN+nZSz4HP4S`US&TPg@xVx`qq@pohNyf#i_^W8Ii5Jfu zKXe$TL(k&fRb<9p_~k5?bnG(EM@5F8J$=Lz&0;7x<;wZ+Uw=G&=+N3npwG#?5FHu* z{?vA6n9n@BE>vB zDnq0a@vtT7ulFoP1P|@hi0_Yuu@LRSFW;Dr_dmp=42o;%DtG#)C}35Z7QOZyzF^gt+6rP6^q+58-PM96SKrF=-jXn*`~1QS1)2XUM^W z2X+PtJ4EPOVF%q=@UA@vaUmmLA(HK`FZUjJ6>H=`&%oY6K|6>%V|Fnm^x(d)LBUyo zxE?38TZXgv09}AFp2^OA<1zLGE7t*zBXS7QY#l)n?!YU$);?f!$IR`AffjZ+} zHaE3RBY+KgG$M}lf}`tRY?f9K8=GH51_RJ0Lu2@nR?5SxBwr3;wGc0MbahWdxRO&{ zt?dFL-}bu6xK`(%LCTw3@Jc9PAm-6P+!WawYlTEcTd=CjyL3>@5d_1f**Y7PL+JdQ z80iJj?kDO^YtK+hBmM|zHGBus&_53ik3>p-8z{SP zK-!L$YSkMtb+mL=(QHUWL=niOCt_&17%@(Uv8f+jOlKs8*+q;Ikvuoz?^4W=p)Qw6 z04du^Ts=CX>TP$ykBQE1?x++1g*pxckBGdQp*~P*bOr;{b!y47v+cCxyrF~^I_YJZ z(>MbSj-etfjqsY2(Q#-;r7d0O0uJ#7(5>JmB0vbZdUOX?~(`C8#pwrPd zH`N_-grj#w{E{{)6XAO?0^q-+@V(MI0p!0s=yAMrvjW2EN$3?8cDA)NA-3Y?x!Fa`!hTIy0$Ow25J?e!Qm1x6AlMxsK8+adz zG}G8m_{ke_3EOC|5ibKB>B2SGmXoTnpSJQ#Q!_NUPp9>Ga#2}Jp5QN8G z^uVvuNjZ_+hlHsS6qsU#M7;?T-A<7Ed=##bt_u}M#~?0k1c>)YbPWD?3YV{>&&8ly z%Hp1kiH^D)cO@Yyr8CM#Dd?=lrmy3Ub&Kxk|72CxJ!8) z_k?F^LJ~-1PVQBZGLZU9&|zn0T`R`yLqBcXc^2D!eikx(E?&S}kQ9Znu!P=kpgas! zY(S)epb=e|o?eKrwcydEp|mb#?GxvN_p=QAE|xY55jFN#&Lb-XRvW_qW&qog#F`oy zLg;BQ1XWhin0p2S_U02YuLRA;l<;5);>ANXQwJx44G?&|^4RDM$30^wNxCbp-vGpR z#)#XYtd_zOBPuZt;h-dSO+7_%p9r>0)z#f;XlnTh4RQ!EijBkZUvVu%{CBr0r=_(U z&SmJi>vafcj9#wgIhsjR#9|7#gg5><{p@+sA3dord{p97e%g;ArhZl!+A|e|I8A-1ddn(vvBG zOynU9d58V3T#7w+_UxHR99_jWF-BL%-?=GcnBlz>6gAoIOM~j z`2VZm_`cC+emD~H&GxO^-o`N6y$lHCOoQ42eJ?+ewK*rk{ zLYY3w4S#$rbZ_8x-z{6-f*XE36oHau-s006s6mh1NNcQgB()QP0ui+ZjwaPq@uPp1F$o*rL?V?wBO0eS%OFP*bAw$ z&9||6O1wUQ7>jEhsi*UmWrZ={JdHX#+0?L{QYDcfK;?+E%lW_ml_Cq&(|#CF`S72~ z5L3VR+5{XCO6v8=ed?MI9Drmyd5f1XRkkGpI-$;APJDE|JKa_0-QN!{JDwC)~h<>8*WJK-{U@)?6lK zG^u}G^OI*uQ8%1R2y!B-wCPimmk=(MLST%ARG=sZ1U4T5l$Ihcj6(-Yp)UyU0_-DI z399)h&eP(rmoEN4%)JSCRn_(Pf2MmgGJ^zCm7=Jv)fPu2IH6GMfE{eL*4jFt#cHgG zKU;0Z{#x4#N)m$N0D%}pCWQ>3s2~uMh(H(u8bSs_0%3*>WVrX7v)|9!=LXRB`MvM| zdEWo?|D!kOoW1tmXP*9a>s9B!O;KJTsrNP5M_JHbKasS-b5 zoHzAcS{2wx;05}2iG*DjQ2O)5@BHIzS$1GvB5`kMV>f2ngNndZb#kJiXZ!{7JdVu$ z$<|b3vbo2=O&3U%^>q^KVdc;yP6*HN96_W67Ugtxl40VhYk#Ev8*jmn0SQ0)=;Mny z)rjk7mlKY}`qW+hi*pbSqXN_spJ%C|&g*OVlTo_7*B(geZMd%j(DX6@UGCR^vp}s$ z%H;6!zg~WYv=`Oi`TK&p3)d3?EP5Fp$joHpD6QOt^F$9rf;GPNy=23|6|LcWNk)j{ z8;rRysyGK+z4rRKoOFuQ?B2cmuBTy*(T06TZjWa=h#LF&W%0C8)KW|obb&G3P?ylo z9BJpytc!XZrUV(J9TJF;ED~K%A0+O8Hpz1khdT~ms428wvmUoKJUZLGOd_GCQsJ-4E_aVO+T) zgH1M;8R}eGIZ0ai!X16+&53+{Y2{PuHvRV*8bXpB_!Rzu!$S`~I3Ch()%A3ze2!=% zAKsul+l*N3Boh-ITwWMa`PQ;UFUu?Jg7x0|y#(Pt@X{U*9q$T0EF(VD=^wk$AqX4y^OJu+=LgKJ z{~`Gr1B263JLvIoIJNEIaG~+((*MHz#guBumVV!s^Xp&aukruQ_JjJ)r?(%(GsYOk zPueH{&gb{d?FXgZtVY#&B<~sjCe@$5IMorcx}HJlm4C%&#g3Ri z#*X`A$M)b@EbmMI`+tA?!8;7)^jN5nJe8Bo5ziVYft@aTEwqfD3qwXC=Xg*oj9bhS zT%+e=QWhs0=2CN+zck-2F!L2lCM?5QrnrjcFI%>BX~?jbxon`9Z!LqP<*SeFHQp!r z1r`#HRp8|paP5E0CHeB*m21|lwDQ4194p@|T2pM7M%NbP=OX|$J5)hRDV`}G=I7Hg z*Urx`q_V>Nd=_bj%24_@#3MI`%0g@&rb6}_73ya=n>+%fCK{nl{^kUNdT#Pi->hwT zsBLw(ZQrrud?I>RI6M5E_#aq1Sw_BdnBlOSS5*~(qAOJrMJm-Uhbv1qaH=wQpKqwJ z&8}VwmkxWkQ?0_}sgB^k3#(nySV57o=Yk->nO;r8DGkz7e-Jkso(l zD=VsbnzfNNtY}$Fk{?=OFZWl3`0R!FUN2w1B1BsYtpYwi1s9xg76}y=78HaSL?OFC zg(h2@;Gzs{yT=s8KvH%xM+UZ?Fol6Vg<(8}Va&NbRZWSTYDvmeQl>JnrJY0viF$jq`b8Q57~W)|1}OfJcn>ApT| z*6UWL9@v@g^x5ycYt4`5WMyXJ@6hZ}2TAki&&zu)GgC&k#cBv@gWlWda25z?vv5(E zSK{yq1WvNR9U!U1;bh9V1683x6C8K`j8~qsq)iL>ALVI&ZqvW1jg_x z0`sq8Os(^ZOG+5w>y%82b!xrCl~od9_lVN6GU@G7tBe5Z8xncrr9}PbG(^q~5nNuk zZr?7c;w@OdrD6lQ@7-dtArtRAp|lJTq{7b=ms^d;GBmTHL22Om4iOf_XPzR^y8=c3F?y0EwvZ{((R(KqxWR+EE?V@D4x1*9X z#u0n3`P<+gDj{~tUAK3ZZ`!n>ba^3*HQ9e^Q*=Y=io!6XKCx7s?yN#a{U)|W(C3W$ zWz`!gJdzw;WqrDo4?=&LLSf7S)3j*N;d7`KcL zj;BVx*<*wfaMeua$jCPnMlkY;-PMy=>WQ4&*I=~0#*&n4NV$fQKhjK7BjeJnk(QC1 zW)jskF^y54=3h73x=CHc*0Q8wAGjUKV?4zWF3#1g92 z&#=auk9dzi{WQF8y!D7b!KQ~E@zS53XiXw%qU>k;!VC0|^}O?f|DwYve$jlvdU2Rx zzwAzVB_kuj2)*o0nd(q%ih9-I+RBJb%gD%@F@x=Qtm*XRYp=cT@QSCaZ2L8TrbW(~ zk+oYfv1e$mKPCTArj02m?Wm(9kQ<|!~Nu z6RkPstmw?FiRm_j-pR72`L9l~o?z6^j?S1ijq`Ly{ZnST`n~l#M!hxZsi)G@AGaQ3 z)IZ_gcjvgR?Z@?!(70s-S=$G!sBdziHcDHKqy(GQ8=~*f45{O|Ah( zG_{d*+L+qtmZw~3L{iG#rr`v#jB?VLN&*F-P%=FB5R?m9M!VbBRgoho(>tRT3iBzo z{!mcJx@aFH6vfnyw>Jf)25p5zN=o4sv0FK52Ou$CXlc2-Zx~xWMb8#m(9GuNdXS)- zq1)$G4i1__A#y<2f2lM_w>&7)wQ|7Mium=hCpc9kXv-Nz^b?%EOFfTv=3NG??D|HL zOZR`hi0+Luiv+!KAQmzPBErSWDF8IEaHdB$JI_vBw`^7eQPW5GDGCG|fEZKxOCG!v_!2 zhsX-vvF2uIM<{m$47~r?h(firLVt+?NU0p-dzP)>J zL$_H%MPq8kKZ6@KI8ElBE}_*{C-U5TeJZQ%VOTYGE#z8NXA?fGOVdRr(h|I18BR73 z-g%4k!Y%;xWZpgS4Es`iQC!UM3T!GffL_fYbgj|mdzSdh;j1C)G8#cmg0khWnYsO+~4t=s_jlEWZcXv}%$U@v-wX(1v zgr88Tu;^p4@U6wZXCu#lW+BYuSMwRov(hmLh1Rb5SR{RPOnX^pK~d3iYNRA;=n8*% zkyAkC<%$G9y?`>IkFCOJLB7sy7Fb38Cn0(pl{NSww6pXfTCglXpSzg(taJ%cK&!CM zGP3~LjtxC5^jtJtXBn%DLPdq>w4%qF%lKBHb%d6QCT!A9zMb7UF-(N=?N~nPkRX@z zQ8a{tOm+4$X(nBzbNQiVj7r<4GGz}z5Sk}#nrw{;*uoSWRxpL%RDK!!GGPmunk_`b z#l`E2S;7qr=*1To6AHB0S?7Q3@CM`8iFA(srE|E*AtKf4ETMH~i9$Ir*D=!)%F4Pk zVfia5O^72G3PQ1k%CL^Vq9T-)k!-HR@Rs;R8OA;Q;-Hx8qbLtwB+^YKzAUBpi`QEv z#N{tB%X~>TOL%Qb+whgs)mn9KT+$g*W(j;F?yHJzEN3q( zN#G*PSc6tk$#MfXp`>K9UG8s+73(V7hPEJ@823wIs7+Pwo=EWu3^C-rcp-UsaieiIv)+6O$AO7ch~aQEIU^@O3csN?og zrtkrE*gi@zejU9|v>eCJGqb5!eXgjSNWHRkF~V)hO<;scidMF9|60P z#aNXc6`MEjsKT*70nUO>M6))kt@d_UM}@ho4Hmy;i;y>MAP6*D-fXcq!CMGEL558& zn|F~RDe24YbxeQJxANlmtQpv#L zp->U*rO;oLXXPsN9h9Klym|8$V51A=LCupyFPuBa$}#8g<{Uf6o12>pWe%@Qr&2en zlt23#_>A+$?AdmXOkSb06Ec(}1o#{_Mt<}4=~)iE#+g2SCMs7>4!p+7%5r9@w=EPX z7|1L*hj}l<*2$SYbNckGEEIn`qcdyz^la*-EZD#dKbt)s$egW6@bAr{j5E`k9?g<1 z>j;FKWli_rK(DvJ5rQAwW_nlF^sEf@efS6Qak<;{09SA*U`|&Kjm>bU3DA%Mw{fNm z&>-M~nE|(<#UUc>n&efYyL9J-+c=qaET42vkW0F(%K^2ZIxq~HNw?`-zLUYAwRE^% z3vOe+ByBpyZAP#UfjNTTHT=?opPvT&zX53lA5Uv4S{35uak-eHf8q~1TO0KId{v}qYx*>CAi^Irw4@n4;WbFB1!wlB$M1`Y+z{M@%@P)3S$P=pe> z^La6LMr8U7oNILviz4n@bKfQ1%z)k41&ikA&Uq_46YG7uJuO4ZELsl3cInU@JJ+8b zE7q0$_B-z`ERrvdLqYSiuEHe?-<|uG4exQ@%6;$s<*QfAH-vzL%zkH~wZtO@{VN2< z_3843@A4v}4yxp8YZVRtl9J9lcnSqinpDaEuFw^L#QZkL(3-NjG*OX#egq#1EL1@M3k?f2_pc1-vk zs?x4zu;M*VCx;uF>UZz1+yS@~ZiHKe8=OXn`^?4@C3EJ=e+z0Gni}hCD*2v-tKc;V z30Bqd$~AKW^4!YWhJE|$L9fD#U{oxgg}&&-f_LV;llKnA=Ps+N-M23eW>iG=c7apK z`zUN#yr^h-(V{tX7ZX1uj}Mr=$XY<-W~q5LIJC3K{IHEZvERx25c2Gq2=e9vKQG6c z1v`4nX4X2hTjqR74nDUFb7#%W%7Ps^S@tylWfVkf8gSlp`VSD#b>zLj3d?xPP5;g9 zw=r(5$E^$u4m)~0a^JYyZoL&H)Vw+Jqj7is_#Pm{&^V~$6p5aF>sYkd zvCKHjy4f9b%Wbzo8p~VM$y7eZ8tdOQ5PoC2lQM_!C|ApK z1-#}(L>{8(rNn;rWb-y|*>A8V3cCyplz4A&5AoLnvWVIP`n_CCWY)rzzJ0k{N6Los z+J?sZ@_G!RUrjte1(u{x%e(B9i{=iLS|Ta^{`QYyf_9{=xard_ciY%b@7|T@0)0um^-M-QP4koMNSR4(>NNB_&sq1wO{=z40Six)!xB zo_8_hPb+qEdFe{o>w`U$Hj~8G`y)X z6|z3Y$l`P`ypg6s7wtC{DK_3IFsCTc{tTfadR(Ckq6VL#VK-g zY9k?i>l^D?pt6Dty3I85%nVIe3@>jKG~`lxk_`I(w9mSDzck>4w8KtGWr>8LJ6m^4 z@Cg0pwV%<(?#}Oi{=v1;Q_nAbSYNIopa0Nzu^#U3@a|(QId;%Zqu(_G!|@a~cnwnM z((+9N%?;jK?42_vG^Rmm`_hj`@Udg;$YQ%Q>GVi1>Ed_kA{mLgukru0dRzJx+XKL_ zf{qcdRTin)7q6f{!zqLN^ct8VUC#>hey?17d6T7ZRV?{y+|x1c}Q_#LIB$+Wh*s?%HI- z01BxF(jd${*(pYdGO_q|fK>oi?AZ&zVeK}nj{{F68&!2UPuAh%4a&l#2g9g!ShgR_ z&vtEl8_?oU?%Z8lCkDZT2hr;L^23)W2~-4U&PEUpbW0EncmE;C2O%G1C?x}a;J$eX zGHUyN{5L(w6285!#xGz}$u?<6j=xGkjvob>M5ZZC%Xot=9(J800+AVw= zA?xQMO9P=k_H(_Tb0CV)Ue5Q%(rY=d<*Qxm5h?-NE}nrGLXX)jSpz3Z#vTz8{D9Bs z7BX*vRJ?xU7J0=^F^TI*T+iz(cGqv%vV}O9#b^=^U;*hGSa8<*#ql;5lE|b8S!nwd zTniEgxZsfPtc`qz#B3B;Ypl=E;S0?IFTS9_Ue2dYV+yU{2mC)jTu@L%n1r>WhufdE zeX@Lc2BiuxGA;)mpqRUij27htT`-qRlEU`bL2?l#*K*_V@<`#*rTNPM6gseX15M;F z%@-6x00wTJuL_+4KYtmQ0zMGhr_d`XTy6mwbS?!4SPB>bp0G49p|z6M+0R4~Z_ z8Sob0u@eO1q$3fD2*h1mqorx40NXYqRV0E?RM%A3kw@Z}AZUAJv1?3bjR~}Xc_dL1 zF_1FV6DzTmLJ!q5^0led-@}3|T98}=zu8k?ZyW?rfOtUgye8*>OoPs3N;e)j0G>eG zu$nR%k9x;i%5mp|)36T82nly+X2OD5Mte$%zz90`VN&_|y?_qLRKaBhv$&G;O3r}| zLfbjt9!uZE`6h0FbH#Qr1DU(PHq72ms~BXkq%=_+N<^J!eio6rh>p#D=Yt{{15Pf9 zc_ilXE_2TjW_$?vK4q3jhBz#6b$p5j}oZ+Z);0lPHyiMKBvR zeY%&~6nzx=Oa(UJ zXJ$x+fB<2`$%;qxdRM9HG2*i%~qTw$tMbJ70sx_KGUfoJqHUZodbwSYm$o1s;H z?^Wqqjf`Y_IT@nQOgn+G_ienuGBT#W#&X&W`W!xx(S^=0S58I;T0Lrg?t8pYYr6T` z@wb5mjEtPz+_`gS1Da&;RMLsF?YYz$EAZCbJYqsI4gZ=w8w2tDg{xK@p(RY8CDtMs zf%DefdF&p#lrPFji^Valh*|#GYSeja6|-ZN&Hi8=&b)=oKV7wIEqO4!7O|dvHCgP{ z<{GlBvC->&=Tl1?j|IIbT20JY$q_2$HUMg9qpSU5JT>_6hgK6^c71875k&uI;#9y6 zLff4Ri8Rs~!T`N#dxa6IrYPP^eEX~2n%aHfW?i9@GB}nIYNSvjQ@WAa6xJzKq*Rer zT%VZuJ|8Wt$|NpACq|Vw7c<-Sgr*q!duSoFg`6+qd=_^iLKekmxwG>YE+lCh&y`_M zgE5#>L2N*H7^#+Zn=~<$PT6!i`nEeBPPZn)18yU69Esz2&Re6mjk_;B9Xa4u|3_$) zp)n9JB=eyf`>-Z%3&Xx%rXio+bnL~i&2%puLX(YnDz znb6_PgUT2ZG_v!#fr+%{DxQH@0p zznNRcjWS2Z>zb8l-9|j*1g_D(>Az7Pby#T0og-0@M>5$g)MP^J;Avr{MI~Wm61gR! zwp4BZ@3JcPwG`#t`uD6Zbum3VwJy$i0_rv&e9IaMH^4DkxWQyT{b|4w{Zdj#qM;0Z z<^4-J_M18^B|L)b0ik{b#BuG5a|e(sWyPgL#c|OVQn?zivNjc2HYHAUbgd~QqNDdC zo;#{m1EiB1%Ig~&>o-^KEyZwrT1mGXqt&mSCuOvzM#dFm#X5d%#?IV+lCfJww;Q{5 zK0f_@t+7N0SMD3mdpAW-|K;gdZfp5$Ixa$=|p=Z zW=WtSqgj3M>`*l3E_2vEmkcZ%J|lxD$KNad0)@LpEVOd3zcha%( zi8w=_(NdJr)JE^iLH{9D>ze^|9WjGpvgm%26*pyB}S0 z<}IOV00i2AoB`jYxq~Y=1IW0k#HNwM={G1ZKmZm=tzCImjK~B)L5|fT?@%BaAdA$7 z#^H&0K6N@Ky*JDe7zY%A?iB^OLo3$;Em*1TKud-_Ai#IPo!}m* z*&F3{TH~%q>jrq*yYmYHo&f%Um{^`Upm0q}FPTONe0EB*oDqwiswP`&n4|z7c;k*V z!V8sswGp?_Hyq5N4#5)_`fJzN1w_wX*tK%)+D~vhlXG|Ny0vSqH6x5L`m}}j9vcOP z%Rc|Y+H?)RLJm5XR;uysbWouKb()QZlVJ2oJ$2eCl^U-v5`o4>mqDyzPlQY4ae35SVUm{ck8Igsp{H| zD2x9r-dTIhIuB=AG&|g6`DQfml9SNACDL9GkVgqRU^NQaMbGVp!hpTsOBT10NcJ~N)0Di%{>W)|>wzlG9M+VN@5RO`L*6rwQClYK+OLI>E@K&P6*5MLa z*8$qV`uvWQec5uhciYLf*28STE~PfFi_eSJVEwx|TK`UnPfYRRa;s_u<_UO>S)3hcf}6OnP`WS1<)ltos^)+#xSfb_NJqQTR* z+E1YSTI3apa=&&h%Un_7QS8^S9Xv*NAYa?j#&BDySS+@TZSOHG=g3)I`lHklVazwu+c|j ziaRzKu59u-OXb#KGKnJgKj%>)J{6*m2m1fAW#Y^ik}}+x_F>P_wc$~Z3&0-(R)CZZ zE104zSiuxw1=s*#1yjt~b{4aLO4sYNXTOm>eY%{xs5itMg%gNU{+_wOS_m%)5opm` ziu4~^Zli*^@$1BZF9v?J?RdDtCrtEHM*pzZ%;66SnDd#s4jQ_(uDzH-l+Z6+zRj>?|93d|e4csb)Ta>t^pUak#;({!ixv1cv6ZckIM5 z$g1$Sg{>NL)F^RK29&)p1lE z)?(G8e!Ke)9PY)S#=!5?n|maiR?A@_#BV-)^h9q|xC7jwH@jYW`|8Q(qOb3_4@3|3 zHVl}JeKd-V{)D(R9c7TWM2;WDo@PU zn!V#kkHSI7V6}M1n1@Gkes1wk9J7g(A>I^+j~^tjz@XS<5*K3Q4!hF-Dgj24hzCK1h0lO=`-(jx+rP0qa6s^EBF>rb5&ImPgJTlgf~VuuzSVE4ZNUI4?mNDS=czFJN!snzX~tx zP!Z`xto0~WCE-$cRZ%`p*k-PJ$C`(o^Bp@6{t{22l29qzG3O!tVf~+j&j1gE4*^^T zEWw;Do&kEsay%D`!e5}vd3%;PV8d&`4nnhJs=;S0N1NS!2mQv=HuXqW_KX>?VIn@| z5CES+WfbKft-r&;*ql8>XEn1typG`>paa2=p~k`jSv8Q6Aqr~*B!R2|0>n23oWLZl zkpK(tWl?TzayirF#$oLMKv(|{ITQx-s6YUe6C1n+4x|7#rD=dGx+?JtIK^Uc6o+S=6Uve>irGbrUS+E?CSj)ZO+kVKLU-zx=KB zh+v3M*(h_KJq4v6y?v?;`+~##h1qy2MU;Q|iz6+9zW@OQH1QLN1V(>YONO82i2aYn zlcMjT`UelhgLLv-bn06`2we_JJwOb+CBvQm+H2Fvh|1o@3wjHyKD=dapjRMz$Q6YX;|!w`Q%1E}6RoY^-+;wK7@oO5QjZw6i;pFHHNj@^C&vSt4?Lr)(Jh@j zH`ki$O`BWFZrSzK)vy^dSo^#>24XcX=leSwYOAU$D|hg9v#Zn&Yc3l)&h2<-?%b+9 z4g2c>cRNFMduprJ{&|+3ke*|;Eg)(f4B@s zc4ojLf8jFgBlE+qk3Rh9qrADio2Z>NJyu}$qQ#5m&7O^l5Sk-NjE&`ID zS7v3k%w#|Lw`RRHYv!z3Z@)d0p3jmw26LH)G{aQPazOvQX@5Fvmvz1NerHqxN&r;csI(7&n9m)Qc)ZH2(bf$fI=ur{U|kCga?1WMphz5< z7^v1r0W5SbXOx5KKfy@AYyYjfw~4Jk+QL=UN8!69_EOKT`w?%J$z&8D8ozLAb(Q|(Kk_{l0=2NKv#tq%PIE& zkcaw@F_yA&mz%IE|4gW1>$ndC6Sc-UQoe>QbPkFebg3A?(Ok6-pBm_;VmH^;TwLr^ zHXUfVhz|98vu;Iet>UR527K|q-~5Z33|0>iAs z*t)+_tl_8N5uFG3d01OYA;5ruAhwDCK7cf^v|7|-oyXmXb-xRRQf`SSSd;50Kn$t< zbS89n0UE$(i2p%tjld8}GM#o&&`ri%Jz!=1a0#;u;(+c;M#&a4dh=u&>Cjeflzavo z*@lCOh`Rz0)p$+{hzx2qD+oZ1ddqv$HggFPEbACxkeIfARBktRRz5d0MRe#svF_3) z@@?98fjnK?>g~qJ-krM~_swPwbInn7{x=37Cr0 z7%9KvM@`}c;SQ>(?{;p0I-qc4z0=rLzj0F|h3SgK@fD}ZRGg6NUO}%y8MJj%w@(GT zlS$`G&uu*AhC!k{DgA_rzT0(!MCnx$2WS@(U2Z6Yws9X#v@a>71J5+}1K;5MB5o`= zP_*_ArC@6h;0f}bVdiH!y`6ILyjn&=Jo_M~T;AkAJEFXP<;A)`aUeLu%uik(b9Y1i ziUHtT^2Cip^J*JvS7`KtXVl#Y%4_rH=unu9KqJ0AKs(_~wpJ2xWi+Zy7{G4^*Rp#z!_3o-lTH_~zN<_q%A2a1w15OZX zB@v<0z(1`@%<_90c7Kj9322AEyT-1=`VAIg?{<@7Y1LMp8>~bS0*LY+HUnZ89o*%T z_OfkHV=o>wiG{uh+U-f87MS%}i~ygL2-piWyJx}E&X1{K_poJI{-Ifwk9JS zU@MkewjE-1rt0g+ z`e4eQjB0q{EaoyR7TQ`4?WyCtxYw06!;oW$!a$>=e|wt-7btDk$pb?6`yS9x!azAA zrfVbX69i*mXBYAPZi?<>Q9^ABXPt!Cll2Ki7ow+GSX@Qvo^^JuUmZWtXzxkJI!P_( z^H>{Y=}KiKIjbVOc9yR%4%52iJ++ehT3ifLf-cGew#QM?}Y znpK_~)~^d}`*+ND1u_dwrul2RS3wr4>sQua$^xzG}>sXWkZ;D$hNvtIcS$O{Q zzQgv~$g1Vb3({fmrKmlq#J~yE?Yd&E?f-HRx74d*YW`SP>@3DgQ@&YA9LZ1|2s<&80@91X8jS3jr z%M>UC95mET4%(@~10ay@957OC=?|T+Q3VAcA~?bnJ&})z)-V6_hp0z#CE7nzI-WHI z-~nCnIuRQ^3yw&|#UH&t5|jxp#59kkhU^r9=+l423N4T=>9`D0iyVV&7BtEm+^CpJ zL4O?PKN-Z3&x!#KF~C5rCof(AR9oV5KVSk`N8p#OX8x~piwVgfzyWrAEdL8(0a*Q` zpO=-aqxF6~P(!2)oB)v-j3HXKVZ#@R@PK$+2n-{H9Za<1ic%sN7$6#c*%vleaSwpO zDvOpT=tQ=~AXBiCKq$QP@+i^WzWfp)HGzGG07H}MlLV_wCBOv|F!d8ofVBj0fdt$( zSwkRr0&kxqY=Euzu=&^2vJ60EaK|{hnq|(hgxWur$c$P7n`u6Av<~RS@5PQ%tevLx@#wK5pasw^784vh zTwA^wet-ut>!|4mSz2vvVYcpy>fXqcpa`VJ_ zywF?3itHTmF!1Bwv+@86a%o3VWbwS*xpU^g5@c02^FId}FuXW-ZcYvu8s<2h4j8aG z_8eJa&3a)fW8UR-&3Q9BJL_JPK{AK6+p}0mAb!Q1*|U+@9cMbMA;+9;;#QzoOc1Q) zevKAd+3t+jXJ^A2(9gB^0O~oc0X^ppRu4$ReYBnE%dgN$O~Z!@S< z>Ud>Z28L6~X1^*S7cw}+6nt?#f)z|vub|GqGGz){JxLi}=8XS_7ff+q!Ba~VeQf!p z-YXZKnXKuakuik_IEll4MrL*B78+;*CK(f5|XS!L0}LbkG5^1gf`V%{h$ zGg!EA{=y|V_Y1lrWj_8?)`ZJ9>J$G{-1%V$3%eFAXLH!k#3x^r|I$*1!zwmp`V4;H zEL`|8&H(F5HnW|!xyoM$B0%9)kz#n>=VAE4s;;#qWncubdOx%cC=HEY%>00&Fq&%k z0T%zw72pJz`k8FIWj#4nREWP(Ees)4v5U=DYZX3DFr4~*RwHv(mYDms4m0YvHI4fk z>uc5uSQK_-aXE$eM735A+1%IEP+R#~?p*l60#_ECqqNG}bJTcdTU>)^^m%!dcIV7p zgr5&R75-SFBIG;J<+O0=f;^OT-&tVGI&`Z4D2g-xtXWLOk2>=gy_Y-db(_Iv<+!t6 z7hJ3}G)MIE*KIgLD1TvI&fBkNWr-3q8Z+Q+bv^o**D<|_#2=g->g{6E^E)l?|{Kz zS!24!UeAj3VZs;2Mr1kr7&3NRqi?)%G_egdj7Y40bBue_XffZbVca~n*qyO3%Dwiw z(WB^oRWIiJHQ`BY{Nv<^@w%dD0~O3kuU_^0N=vLA2|TW zo=_U?x4Owe3#DGEvpWZpf_6Qy;0;0mR}fuY_`}~s4+x1yz{fRqrgdzsCE$jmwS*KT z7YvrE1TGx7X0w?6H+_5-D`rr=wGTky0G^#y@(@-Gz_gmOad&yWR$Dw$Db@SgYsCj( z#2tw7?U+(ApC7nxuuUnkm3N_fvawl8>Aaho+^s|DcaLO>Sv^AH4&cNQfs57ZVU)|p zvqdXU8IGNp9c}ECeuFnP9eWPjp|oD~(7i+P5C}9#aaR)4ahIJswxaU;=;fsE9M6|b zt7vB#T?2A&5^rrWc<$f^HhR<{%>)_v3vl&l10KsVLT@K`hcbdrSQfv>Ja#J){Rngg7At zs&-IraIAbcazQ;Q<@Kqlv25am0UDs83S1iocUOa{T>}7eTO8+8yVFJYz7&mL5CFLi z_2}k-L`b!4IwY6qtK3&-(8@YBGsJjq7TCvbAxb)?_HDx@Bmzt58(C)`RMv!oPC{4X z3S0wZUBZE#gTE!Uxfk&fjIFKLC8y{x}~n>*lasqess zQzlE8sBKK_Hx@wc#v5+nYEsWFFOw?KPpp{5QEaq zlg;lKbSRr>Z6qiLp&s~CFMn3ZrO?m%zZ+ezmw`3)baXvA=imQngguGEhsa>`y!*+C zk3amgKRobk`jGQ3ml`Bo8*+nzbI<=T!s}g6J1_?5HvK;huXi5h2y17~;Mn`o2~Ygt zl1bzLIO&qdANbSwi6lu6n18(Ji3cYB{+T}v89FLBxo*_ce|R8$$Rkfq9RKLVC!a}w z_}cr1p7)6Uci+$$@#zWAjVJkvYldHOC5fjyan$#v@O7sSu_}jtC-I_3o=l%KWa#Y= zOq!5BsgI_a-c6=vxEQlS~#g5zckw23|z_m9O7 z8#%YdR>TkOzecAHsLDV1WVd`Iz78L&-#VJqZ{b5E#2 zhW{r@^C|kdQrR>C8R=;y$rl{d$!{t3qfVW?M5)1Sed#E^czt#$qwjiKVevYzL!}!2 zGgN|%a-V{!OrsjUPk&TGIA^~mViU#koE7> z#l_z=Wni_wpj)6^cHE{JUeA^4+p@Q=GU=KYTB?@o^D7nU{sU242y_mQFqy~Y;vbqa z;7hsim(Uyjcj$K4NM5bZGt?|cc^5?KQBW=AjZ&of_wp{i=M;&$-@U0iweMD2`T4vO z|GWd7tyjYgf2J>QQ!Nh3#ZM#ptxse$aW!D09v9~}+Ui2amTxgU)O>w$TbEq;Pq-E& zw|A*xfVqR~l=_b0{ZlDI$G)l5cMZkZRBEy{PLJ~*bX*TcZ30D4pZ7N6Xi*%E2s36w z47U(;o3R=uEkwl@gEUCz6RgfU<9s0Jl1I|zTF*tcP^A0=myl?Tnvj+ANJM+KMO@# zdDDPksObf{P%nNV|NMCPaP=Gv_HV@+$xMWgrkL|7>a`u~k<)4OeRZx5*WmBd32w>sMVq zPpZ`KhjJd-)t}Q#5p`9hgm}2OzJAyb-Pk?&!YmD@62Cxl0QSK2hWG- z)+zrY^l;^FQndpl#d8%gfb?+k(={gIGzk9}VFZQkXi zt*}zR{fpk9HE;X)zt1GHgCAdDF(v+jLe+KFOi!JhnAq2d3~qsZFDr>Jk1~_4MNP9l zaqp3>L*M5?mJlA?x67GiQ_&CJC_~|n+(U)UtPfCsCPOuTCSIs&!dXV7_c3bvH8ox9 zQqy&$r?m_o2+t;4q15vSc(|HDdgcmqpb`0rr&Jp%W~WtBLzN9IOZ4LE9zYTAafq58 zhBB;mY^PG*SSX{94;aLEi@H8W`E>udF0_u?W(xXfvFS+T*P~+>V^l2_HZyp3SrH#v9a5?TkGGG4bR9gmW;{9s{ zp6|X{#Pt%B&+xpQbAld7qYAOd&@iLbIY5C?7u8X*bXD)heh!MG(*BHH&%ecCG!K3b`>NeWv^*wsS z5Kz9asQRy5jy{UZbfouDs^;0eU)6+>3jS`Xt{n)gGZ&$~t9LCGp-;^GSBDzyKags# zFcKTt&#(Q*^mWI%##tvu45i9#mWuw%_|c)BP?8T);giM#Uxg42{$eS&Dm*6-yJ-%m z>zxl@A~fcABaDt)4&K4*y@h(ZZfO3&`HZnI7@L(%ID~d4G*U<9RtFj2zhuPBW~jZ+ z4Eys1Mr2qQFYkwpu^V|ek?)W~Oe58pKuR3n|IcmYfj{`_b>x8z^RyiLL`B?4gb|v`LKGouICY$d%x#H#b~&E zPJ@kn;=jcuHT75q6)EZZPWj6Ezd(#cuK7Deid?)=)2@KVh@44I#N=^D_2H4IPK?n+ z-l9OyLM<^RuF?W%jCK`rEwsC+1zG=Z)WVHov{9O-X31yRAKPWq;BL~R2$eKUgfM9R zY1k>6dQaDQL0drDbc#g9f9u_4%pJg!HC=$9U}YJAgD(llp(6`{vsl zL~GD}3F$R`|Irn?M{|QWJ|cMIe`6#u{Sem~4YzK9#+VtrkR$Kx=Mnv~?nI%0g}g_S zcWxoQ&fI_POR@mHy>)EMW?!U0KZiz{FjZO2v;h6)$;~ayDDUVIX^_f6U+@r@U@?nM z$Sj8=8U|8{JL=P;CmhrW_vc?6L9Cu^{asH6rC6KBWOxPXnec86Ak7~7(K$%GLVO4K}&g@ZJaBU#6yfB0HbJ}B2Cb}n+IT6x_=Y394~1eZz91+=9ffU z z7AZcb>Yt6NL4~Y zWSlBD_G<=;M&=jv7BMhZ$p5Qu)BB=7_$^nTL212FQ)cK^`Y&`-XpsMQaPt>>Nj)sN zmD)t%^897QTcILvk|q@dBrT~nys4!;k6lj4uqs&+6|jfvmoFHg+vPp03$2tw{zv=6 z$~*b-b(-S8q$!KKtCKrz9w8+4wx-MoD7v@(_cWnMKT-6dfFiB)Td&tmIB>SE^sD}7 zko>>vPV=7E2OqO@@J#$=p)c)OAN2cI-#tz7%likf>3Q899fXqZF66getVo+VKBE!z+E8s`35T&-=-`S*&V@Otvb%gTv<8}YUFBhTw zs|lwD{Vy5xKa)WpNw{$IxT^+VWwgcJU(oI!Yv}ZMPq`Q#YQ37UIJ#)o%fGpZ&yw>7 zVT{b5>{8W_0l|hw&FPA~iEH2y>#3|2NG1Mf8B<=F`A$iPzw-|`N%;0(5Rt;K``%RC zwxd6&RQpC?jd>M06YVn6>$!GSGZM2?;tJn;3T&zW4%K!iR+b6Kp7>Bif9b0BTO|b5 zJYUUW$!PSu$5rc9WPAvTg$xmUgHlJ%#UAt^3J;28^q;=^3aK^7fR_dWlw!(8`|eXJ z6PbU|3FZI!4EjS1j+IjLBwx9VtBCnN_h!kb zGya2Q7{>D#vt&3bg4O))3^X!vmr^BY4^fdM7Z4rk0&-U3O%U9J93?NJAS-4No7g2k6SM3pBRo$NqPWa#C zm_v@Uk@Py4@lwg5E3EH#jkxQzlU~uc$?|)$6p-cn$fSqky6=N9aP>ZQ@G`n#G;(D* zo~V)AIa!arxx1~=J3RGO62fAy6H2xAq^{?w>mS{ZOU8u5%71_i=6nRrvGVplp@o;7 z&JkfHzxsgfilQUAeGWN>o@79yL3H0FVsBQg@RLdw!Ybqizk-~5b@z*tF8c!M^+L)H zM%JAzZ}7t7%73YQY>H_HDd?_=Z%<=u}(ENKG@1k2K*pz72g(%eC&=SX`mcrxYGk5q{AU@qiIRv8@5RCc-0yyJsa88AmocwbUj8LC`O(jr8 z07nArJBu<*y!QoO_3!2anh48qk}T>$!u_3~<=>?BT_duk*P{@P{Od%x&cob-f;U$D z2U~Mr^P7BRwKE#Sv%jn#vFqYxEPVeuxsV#g!Hc`3IL28{q@AhVaxRNk2NYRBqY&aYfxGGhUWZ{<{(=>dS-?{oRLBFw{Oy4 zK9gSX@AP@6==9+ws6F^yJj!53hL}$-n4r)jFChU$5D5F$!O$NiSonPc$}O}>&LbQcMZgGukT#yi8tEhPH+IjIJBXH zao1eGtFlA7DUiv-Dsu8j<*p`L#pIQ||Gob#s4AxqtyPbrGaEl!(}Ha6-J_xYd&{oo zQ#aZAA?8qz@83v9{5AS#b8f3Nev;OG?ih(w)R*3uu`1{Nc`bo3%W4=NN`y@Tf*l62 z2-70GhRGXTi}1-pZAM*iEoJeyJ#{UDbRg^pbh(x~y7LS0Ei&4f*YfG_z77QMpzQzY zxj>46&@U2bpeP8SE#d|KQ94cT+npvt%4y{z$oD^`$uo-ZBxSnuotY*QNk9vPbWsfC z-jXH)x`;9+ijPfSCm7#Ci0?46w{ja>^@2APp-~Z@H z^7zpwN6OVF$@I$tnu^^x<6OOkUAq-j3fRD{G`JFp6ufa z319zOZk}KjC=egFZzdl;OGM-D25OPG3uxyXNs(Xtd(uz-EvfS|jXzJ+Qt|sTwiJGh z(kHs!nt_`9@)_i3Pm}pPNP)M`pgz=13M)@k9|U(i=1WdHbmI&q24v6)2)!r?geqXItNUYLWs>@wQg0BCX6{?rul(!P z>^JAUdl@XA@W6|s3$vdcgR^GzUn1i1QOaJ$2QPZ;y^Mb$;kS27@~fv<|LlDE%}-m@ z;Y{{!h(3!Vd_58(AC>5cep-lcL%572K2WO;{TdD}@bNZcK1TnF+!Rla2_nPcz}9n# z@BIZh)K6-n%gfk}Jo>y+$K%NIkjU}KUg1LJKaaj3ALaJ8=_3u}YdN_6w+^bvjnvVv zUKr@tD?q--X1N`IC_n9(@Y8=IeWbz&-`7cVHAik@6d28fCE|Jmq$;h~+MSb;cZiJTA{xgOjoza0% z9=)YWb@m}2Li%xDe5B_;y8O>r4t9L1@~gXE>0ubx{_Y?z=FeAJmv@Y~{ZFMxAO{J; z&=Cmh?j%XSa`Z!UPpEwl;oaLYRaN)-W9QyU-|B-ojc8uJvpq>gyn|bE?qC;+j;ZcX z4CCStqAudbpBJ)7)j#*CtC&y(pOP=t8pCe|h~zE3q%MV?9T(oBBak{&~v!B-a&%=>4_`=IPB*ICqG+!@iV40hoVRAXsONvkO+_WIGGlon9uXG*RvxnRB z(1kin|F1OGJQW8HEg>hlSAV%(8$K{~qbx3#+@nDP_?srFpe)%G$6KnClJwkHt zWFYcnuhAB!TMR8H{rs(;!)tLteYZq<+CAR5pC>`9&Iy2W0(@P{7r}E8+(}&m0M&TB zi<}JnH<9n2*Rv*Yr?^Z@&GPAsXM%*u5@JR;T4EQeeH5R?i}>M@$w2MPQHHXd_Xj2x zcv>1BNbDjhi|d12i|iFhyE3=~>6`HVl#@K&*Kv%RI9-oO=i+K2J`ml~df;vnXb2+d zN}g^Plwc$WX(9{|6@juz`rdMX$tP)XJXatEOZ|Z^E|PE@CIPvY_K2pzj#k`Pu1~|< z{kazDIgqj)o}=d(=e;NwNY|{y(DxMyR_^u9YhoBuPUe!d$6;7>Uk7R3*COaho#GZD zZ4r}1SZhb(u-1#RNucN|gsAzlBntQa=cgbaj!j9K@j2K=Ffm=kfk5v2u z28e@ctrG!1uuC zS)15jNRNmx?N_qSfIA}$a3K^=9m(gR+$VLq7SKuWCnegGlv<|smX~AMbuz7`87HpM zl63UpQzTy%=q~YJ)fx5-j!|&wR_YmD{bqSk-+xV>p1Z*A)Pb}09iq||e$5XwWnDi# zq_n4sHYBtW0D}_S*|?otTk)_ECqiijkI2WuU0K?3`Lkw#k>YK|xsMG-3^!l7l~@ro{+?BIv}^8Q z!x$S)M{o%Jbl1U~lfP0=5?3Prjguew>d9-sHnaKAqy0t`HgQB;813ocEnTu zlNaHOK7JTe=iaD3g}TfI*#JpAGgL;1SAKXq1F#IJlrR&W4>N4`fc zYt;jYoj~?l+E1P5`^AG0InM^Nor+kH#OnG$(ib>>fl@oLPeq=kc@0RL%IP1IQu+QP zg!^x>%BL%|W4WiipEAE%K6-9c91#^B5-kzs>E0Tb{F&|XgG*?`b|C)nSBV`Gf-JC*;Oo@=dlBb|2F>AB8fLB?ahvhVJ^_p#UO{S#A&;^p-^AboaORQZp=hHR8d^>mQa+u#&O*DTAt zi>O=P{q^*G#Qt2V4J-tS;-adE<12UW9K-~)K4W@CC-^FQIh}zz*A4G$>UTT;D|~14 zK|cD~P;smILL~TDS^reU5_OPdKZf`><$`Af)iNv1f;W**p7vEIdBQu?I#E&dJt3aocAdn)z&Qxk`KTAc zd?wmKS1PpZmIP?*FUM!ey}reNfo7o*M81do^0ThU#q`D`5nm#4Pq%sh;z_!(`W1Ix z&F&)|1AdZ+2(|Smm_SaLm;V-tXKUW%iIejVt9rDXZlu29P;)0nq>lSr8tdu~G`HbN z)*L=~@>tWJf;v~Z`wM=Xgiw>n8X7)@#EKKYAKwFO$Jf$O_u)zx1&R`Z-M+B3Ko|lBL&Ts!V1NT$3^8CBUbe9To0kC_o40vMLeGBf^INsMg>aIYIdgK)cjtVK zy;ZxaR#mN9_4=<>)wLhpC(CvCZz&zfy7QBrY(}G`E1;Qeax;C>l6yW!~nmL>8V=*6imk3w+X>0vr|7Z@8N560ww`&^wQadI4;o?S`4GsqSx_52-}Mu^bI{)Re|(Jxete-T8z zIi~>;s69#)N?AuVI?H$AgE=?#th5k^et2h!v&fKgZ|Z|$B&@JMRHc~sYx2i)-vMHz zNQ7O5)&-d`LkvR+ULT`L@ccmGYREwQ^GONWlV~rTdHV8on8HjX@4xIu_0Vp~%*2i# z)abNuA!wAaC>mS%D$q(@N0+3+RGiHa#l$)!D9JCsWFz;=O=!RHtTB))gANF?~$!J{Yybd~Qg ze^VF)53r!o46}q?5mHbThzFys!x8%_amyKG0(`_~9}j{u6}lFAb`MJ=H|Z{dsx35a z+4H60WVA@RGOq0OeG9=0j1YUi95jideDO=XbFSOvo$U;81~%$cF9mtK>u5D0(vTSXO|CZ#LW z?s|pwTEC?fg5Uzo#4PI9Iix9vy>xYCoYB!*pfE8I8g%)u1RP9f##QYazQ*BkLc+5Y zNvR@vT>_SNk%XqGQsg9h$lR(xe)#ooL(@@>a!fYhgDnK(bbL=`Fj-;O*Cpg^5rbHD zD|Jo>1xmKILAB^bSwa?iof`mvpIf-cCqfuzk%!Y!6!HH3%t5BIahtDOYQf)IbmaXO zM(>b$Iu$zqV0Aj3gBT=)kl0&;#4e<;ayz;fV0tmZ=2A&>&9DfS8u9+)EPECq7d;AL zrKCS&?E)-U$^uO#Lix2tN$jBqKtARne!oa~aI8`thghTvZToAOoat)9+h3@oF?^1K z;(xO3A78&q_GuqSPeo2+en8Y;<0JRL;IER6iWxE(9i%-^m4dz#@~n208jqPef-8JWtFF=>~p&0cIYM`;Cx|v{R^gsOqhOm5`|OHi5?8 zra7ByQJ_$*{Q*B)C$|fan44ry2!v;t=F?WZbT6<~RRF{d@vxB7}cde^;x5uIH+V4KfRaZ+uHK4F{{0JUHG(V>JFqqJqcw zeZD|A34g5GE*z|p`676E&xdjYETU39ZJPBy-S6}1zOQBB|eVZ$Li+{0~H6+-v<`wQN2o-Lj#P*9zV1Dx4f4)HJD>;1< zjGMY6$kP8v6J}wCi8@>o{wZj0a4@`@%W32kN;otOKK~39v6urd+?E@70;o(_(0=(O zcieRT?DQtA#aWoxIhP*#0T4!iANa!&j!@zaRKAX%9sklf)7*a#hejM9^J^6-`1!Kg z`ei7dxz1Owp@=U=jj1D#nWlLYGr2$QHiHpihZomlMuq@zV*0;J9{e6aP6~Baon^f4 z1AT^iUjBX}ze0fKp1r2|DW~%M`eA-a!m;*_xE*zD&ik_=t2ock_FIKadkmBO+BmeI z31PF`slePw=w{dJLzz>qY-NbK`+7iz*y0Cs6 z7zZeV5LN0c*!eeeUuv848ovtBw13*bss45}s_QA*SDPQU=zzMFXeKXncib}5JZn$7 z^cP5}fS|w7roO@V7bG*<%Jxslxi`O$=s>**e&>hRIH=YUf5J33*pCeb13^Wv=!qWl z0h`60_t0N{^_%4+oQ8&6apQiAgpT=Siy5BAd7(LUx!bG>oxSKc7aRB$e`xAQ2s!Xl z$UJ)wDC;RSkn@S6J)^UR1^}j(+~Oxs0sViNA&!Oy7T>$6|1|z+Nz|Sx^f8%MgW*rF zVt$}EaO*(Gd?yvFhq;>H?3ye8zx}g#igr^~+xGXtd5eEtD7Y(hei_H}g6-zIL>gq3 zUo1QmuG;zg;-?9J!oQPLZa36lF4dKacl#Sq@6!+JN?eYqECzqVC+;t(^__vAYancNzTq_ZrACGC8Ly$C2b0P;wShj*!Xu z#AsOTE`!)z1{{X7?i`5SWq>GRcNxU)GKk$}z;Cax)yD2Jh}~rnyUQRPyUQSUmjOsR zdT&AOE(7cSg4kUKoMDdLWe~f|Aa<8Q>@I`YT?Vnc3}SZ~@LQ+w1F^ddVs{zD?lO>1 zqQvepkl)ZAVs{zD?lOqoWe~f|Aa<8Q>@EZD9ti#Z-3PI|4F2ei2(h~i z)SU+W$ohZlE`ulJr`rEF-DTjSKB@c&eLn8J&yODaQf}?YS8H=$7&c+#%TiZvbFjUd zHFns`fBDKw>6wMr%eM+^UK&=NUiIS3BcFfy#aF6FJpJR$N2=7nA7@4)Yer8TNpSCP zJkh%kfdgNB`GpZbdTH4B>cX!};%{|1kt{Q_dt_C;SY4Z*`Tb$FqpQ<@@ZyM(FIT5m zyf~JUo*DL%x^F-TawX^VCwvil<;{JidUS32OQT;M`9clh!>VgX4|`$su$uGz zv?QyY@WROS>KCg=67s^Z>gQh>_Wa27%#qc(ul(1z%bG-@i+36Hx9&Nx{p~~* zZ~NOB5qDlB0ZvyMUjOVtEB;3rmOEss<&N=K?q6kD?zHQc%XhrxpY3nu#t(4|BJTGh z?sE}$jB@!CyUQSUmqF|@I`YT?TxFNv=AUtIK0|8N}`~h}~rnyUQSUm%-Trt~l?pB^=^&0D%WS zG+PF6CG}{&{A1@P?Lk4a?R(r^9#D`YcsK-JuCd+|2f=sv!pJ{I31G4Z z-+$WcpbRb;Hyctk|6@>|G zPoBEWM?3g>!ShV2(=NIB7?HX3K5(@8JRdsIezdEZkGY2aLVcL)x&8T?e78xylN{na zoqXB#8|snIAM5HXuM!r0kLNp1Nqpti`u35$KcLRw$%lFF=dr%yl%T#@B!2m2lYn>m zP_g*s8$ zV|N(@Ur=9q4ZX}~tAqX^-);)uejrKfX$ISGUS6y|p}Ht`mqF|<1NG&nTR)aBV#V$< zU@pD6>L31&FPq-|oz}l!^t)#B+S~^Kuj5TKSj2~Q>P^1jbN`($we}eHvHAH=n4!PN zr_qk2@Zr$E;R`kQKmm%sEFW8S{+90p^G(ul(q+*0ems?Sy46%D?xGtHw8qQA+v!fjv3pJ+Y_8+wlX zpZrWd-*EZc&%$D(Ym7DGhnm))oy+AW``H#hPrD2;E?#%tjh`9yV4 zbDhNcD>-4_D7_?R=kP=_3~K8Xk?}EKxE>;(%@!JL91{ZadHe9Am-vcnZiw>D*P(bl zN3I~p&Ml+r^B|D;(A^5F+h z>r&c6^5Rp%e39FqhYB~ z=XcCdh{7Ov;9V&s#HK3uHzTBAC>S&!)IzgWa38Vn^Wk7e9Eeos;RDyE$s=D%x7`(% zNNzguVzW=6jHo?d?uQn;y_BmHXID1%=&fa9&v&YL>lQQdiw$1#xQD|#^2u`(ztBip z`O3K!W_>d_c9(&jMvvWPV7?x^%Ro27vAYancNxU)GKk$}5WCADc9%iyE`zZ7NAE58 zLlLpN4E|T{G8o8@v46+IFZnh5uoqq$HH?@4-`!y#JUMvcdA`i!jlAP%kIK8rKio&U zy+0uDJoob4`DdH4q?;#Nd!a)HGY(tsdgW-g^~n*x^?P{B6HNns-Ah(b-PDLXH{!k? zajlGOb-N<|!(2oMf{O`{^eZ6->6*v zOu6?@{3Pt+xBA_l?)k_6LW|Swc3tPQFUm!&({(#%d3AfTE7g(Kn(9in>srJYNKHw! z>v398x2L4Wr}fl2^~6+fesNiRd2?B5zdo5RI{{e*5^#fnyz~_A($Evratce!2Mj3h zslBJWa>~cm&0n&7#foK%=S?3|>P<;>?fxq ziI(g*ezmo;v+eq+9g9Zgt3=Coox0Z6+1YyS__q1Otwd$hHhp-dx#gCymy$DJ{L;Ob zIzr*Vt-~uPlv(UeTz>FsXDIBy{?4+R5{tb)Bj&&LSyRgmNn}sWDjKoqNJ}UjyuNdC zX_h61;!(>_wuQriD_h1Fdo4j2eTL3IaN){vr*;~Vrsa*=cqI@H-q<$QM-rr*V&vEC zxETxwu5K8flOm#SM|yq6u0HhMx_h-I-izm+Xb**&_l@_Z=(>cYSFXAk2#0R%7@Lz! zGj{07IYXCh+c5n;tyxdb9rspaFw}8!ZogFGn(b+&i$CcMhuelcl1 zX}9g3%7%;naNzQqLFslyTekQtwEpue1{ef%*wgw=tZ$fJ<|1QP(X3;z(X_YLM_Xx4 ziRlAZoNW(QQdENS5?_cc<2XE~Zsn7#6D`z(>t?uIz^5mScJA|9l^WjFrJAA>q zwVRH$ghFi}&Mb5hl#(}Qal_m}-sA_gV|JsW{yfZF-7q9Gk(Ms3UEZ)@-uA13u>az! zN{?N)rxj1Cub*6O*rC>41v8Gq%k6^`^HOM>0d*^vO&+)8bVn%I_|`ahNz5ELyJ5)~ zB856JYs~hWuyST$Nt!(+cf^8K^QtN)?Q05!+K&<@M9b^M|gz90&(4tsa=3Y!pwfub)($W_N2R;A8sX)=;SB?MZ#oGAm}T zT2fu$rSEjmXnSgWsab>OG%Owk7ebquHEP?9V7T+_qB3t@)xw6kgR|0nHGANKo>QEe zKX%EgS(Q{iO*>^b%9ow%3-4{E3M zl>BM$2?vL!R}7uKYRT9>E_-Ux?BmjH_Dvcvd~U;{D*CWcCVH#3UZ?zX_2VWiX_$lD zyL#^FCDjv`u9{gv=?`hA?P+C8C_Q*%*SvYFR!%N4kiLihFGATqcYec)2}NmkgRzy; zXX+to&r_S%tY1pWw5h95b=j6<8`rE`I3hPi|B`k#(K})b1^cfZd27?WK}cD8gLKuf z|LXfYH_RH4p7>?0QAW+jo#9a1rK6jtQzkvq?x~O-8fv}x{`!fu6I7Hr=^dE?ZlB#$ z(@!>^zVhTD$MrFEg8gHAknI}~btf4UmS49J2RM_NO{=Ka#`zM|bu z%pAJmN~iz&HW`lb+HKXw*ZiGVHVn;7OxIfMsm1d?Y-~Ecl(B8cYm_fJ)!2A!KHZR5 z#9zJX)R{LYQV(LO+JtSVPi-E@@XF9y?MCVJHS1Or4T|;h}N}hH}vFGZ=aHi!NZ3S9aLVF>tP0u z*RC*YXXO`{mY0{67WeB{R9KLg<&m7?wUF+yCupDRc9*wk$oRT>3l}e5v|#qsG39xN zOskh!1S?q?mo8g8w{F_x$&<#98&Oe|MHO@{WS0s2q^u5urY_sCb<4(eYu9hux_R~7 z>XOJ}<0%=x?9C%*&K^Iob!}WjL~?vAGQTMKV4RaxU5nUR-bfFW?tp2eb?GrKBjguL)~WhPh%|! zTv=0Tko1(E?3?&5{?FGBV)>J#ZT_ZrW(_GHFt}#^_S4s{f4p^kK`K4uHj#5qc7y^K zR}M(CQs&eiXbA;xy@6<~l!4aM>*vgvwPe#fXD?m&aO0$6kF*CV`^`JkNy_?iN%@hU zm{YT#8NKn%DwYDuJkb_tKmS%k{hA#gT)gr5N1G>?q)StfuxK8uZQ#->Rpe==vf6`^ zaQld^gl!*hU9w===Jzk&x^a3Ppnx$*B@1Sh+9fK5m!FyLHAuO57~4r5V*dT{uBoc+e1$syy}zIj$3cf=*Or5*y|}-LmSSucHBHT zP2naNqlS+E24%A_4_g0uAb9)WguFB(-6QjbD}Ta{%dNN1Z5UTzFsu??-T`yozTVn+ zV%b0v0HjLhf5ealLLw2PqTx~?)P7=4sjr~4pPFtwWwZ8QYW)02eRaPqkCC2NIb-{| zn>68A);#9WoWb?yI{c0MCl`5=6BW`8b)Ijis;C-2CSE2CZ{^%Q=PsW+xN`E)^0LaY zbGIJ9c=6b}+5-B&-IY~7b^GPc!0qEps|q~HuKa25HU<3Gch8wTXW_I2Z67tQoVn%5 zN5}T9Up%XBZvD3RPM+AmVqBrj9*IW3am(Mi($>**cB?EkDc+$CXPet@e7Lc`Va0Si zGhK?ew07y%z58}eLZ!|Tg0C@koclP*gYKiMR=G(-2;jGGcbwZ1N3a+~XDaou2l83`?qj$EkR;IYrGU?PDdb*at+8+0XwS9*c*6rU^TBT7y zNy&z<^cz5e6mRjEnXKgqzhc^|?fIhIcq?XYefJQrcB5p*_Twk^KBcvyJ5+D{=={+J zO-s^~l?I#03eHF=TX6L1#XZwy!M3LcT!J#LNe4cxvK2nBBG$>|l#&UFMD_tfM8;S#lXx~o1<8plC(o=p>99jrqOk$$o5 zD!`fbym-OccK=Nl0ednHYR$`Ph1RvRK=2`}K51#axUovjJS8&^-thnNL@c23+Q-bC zGv06RY(6Y1qn?~uG4J5jjz3zEm4<@G?zrjixS$kA>DDv1o&LAdBxs-5J%iU@^#^Y5 z5G1eJQ!-1&E&BJ8qzzbk(I0GicT%1xzjnq`*}t8nSx+ln`dMeF{TL7jdr)Fiig8y8 z8eX?`6CCw{H$Al!eGDyePEl&I%kZQoqD*v`b-Z?62AkNBx@3MQ>8BKUVs>F#T28-y zJ~bV7YbHsG&LE1y7n7!yEkExMwjG^OV0g=_$5nzi?LBbdcc(>n+kDOMzqVyWmQgry&8B&k>8KLj zSf*TNrOC?|u^@F%OK+hoj^EqpZ#%oB%#+q{#`Xhit9>bUw6Lyj8HXN_>>aXj^D4B* zuGE5)=%9(rWXErJ+}Jy@k1MTs_Wo1*=azX~$!R4ERj&pU#p$0XrWH-vxOZKR6nb(y(jq{}> zT5Iu*?W1yB$;m0HnT4ZP9R2*}xy|G9m`O_(o@l&va@DAk+&&}Lf{dFFPA$#L^%V@L zS$X8*xvgZDnlogjEXMmLm-upX^NK3RFF$bZ(%J1((aB^CpKfXU{GFvkN(ax}bDf@g zynf=CQPmUYZ8~z7R*3Nlc0Z~ny1hxTu%U!7lEHDk%z&0E&jFPb}N&fEn{ zSFKxLKch0sWfTmZws_4ZD?*>$K66^l;C`a5#?dvCa|X{`GJD*JaYkmJlFGqDhYuN4 z88^UIaed(6Aww#QysktRXtc`L?K=?+g^ zdYe*AWq~Fj-Vs0NvZ z0bZ^g8r<2DIms`KYru8yB9Do4bBCn#p$P%>2uDSrDJ?4$#)d`U29j? zuUfZt&!PACu9_ekSAuo}L-O*I*Dt+=&1X?nzZ?%eD9(N+wP4ir2?M;ziFed_0R@xl z7H>X${?bQVC$r4P1MO3MH9N0&w4TMDbZo=y>VYMFuyLfgGK&V3`G6czZ9akvN{7~M zJJ;0o*(N4I#Q(3DP-h=*25-MPHmCK<@we74oHlk~u`kn;nU#r9#269P=6yzLs*zK< z;KSz5mXiz7UE{P*QA`FcJKh`!wthIfC=_UKy87|qU2Eo#A6Vc`!>AMo))IaG4oq<6 z)$VN!_&?u_qM{Vsyz12-eBK`Dytrm?T(~I|^tUx#IlXV?qyae!Ym14wn@TTWaof#c zuxXFz;i&jV$+Y$RPhOY#ZelPYj3KVG>9aixhG2Gz(~f*Gaps6ONZh=)MiK)F2G3i+ z`OQ|iQ!hoUy)Aru;<%wD zVnf6RoS0rQZ^yn(^Cpa&uyEgXf54%g!Qxr9`ATQU1+jOilvMoa{Gp}A#r=v3eDp@q z+-@PZv8&%bvS&m6vK1Q-ecl_p0zjfz^C39xh&78CR ztqbj_=3-eanSZ>o@xwV{SduEX0miFAnEVjh?#dsxd}IC8p#$Q|ItC1%wf6=Uj;ADc zdFs#Hy0UwM=-WVylU7lpYcWG+R?J?%c4lR6dYo~}lRIeH*#eI&LbOR*!T4>TUHfR| zU@;2_uxJm+UJV*6h%(vJZ{oVQu%f3i-<(XulJ?HHvOH|9scD%7Rm>AUbcj}+-mhlO`^Vp$hS6VIGbOKfSAE^sL1owr2i7j#^U>J@3$Z0yx;gr}MTDRweV|$m55Dufd{q0Y8 zh|s z-&imPJA))n%^$UR=kZU@99%BjAM{}~+HEtJt=szM&Yin&oMP*JH@2|P(7_2D4%0kC?mZ z;PH=+?_WK+G(C~|dF--XhmRiEFmrHm#l&UX4xK!4aAkGA=p2dZWwbtBWYW41ueG$^ zIJO$Am_g^*cfP5$>C+u`<)lw5tle<(>a}xw<_{(Ps<$sRwcI?}Aia*1kjOgSDBRps4bnn z@BFRSj<$<$)aIjQCd=mTZs72kZP%DH8!)^Aw%DA#iCOf3Y-FkQD>pA4CEMfd9!`DZ zgENE`iX0| ztzEh2Qfu4Qx27^*jbgrp0d^5LUG40+$i{ghd(U(Be0Cda?7>N*#<72E?VVcb*KA%g zZQaSu%C@-kY86pDeHJ zQ$DSJ*?4AeW&M@nXP0MYmd!nMv;FqjHCHwbmn~POKws^iTb*rZmJF_#RNpXj_?U+C z?VZ=Q*OXVyTQz^Uv?`luiu>8+9#__o`j491Z+&q4&@}K#oOaHhK4|rYj?SxZOst-} zdRc8r*{mbC{kM}vtN9pv;D%(wHsDX zD@{+wHs9WP>+t$DYvv7M1jK8X@1zE&RxJ9Uv7NytwtKPRdMfKbYwv74z5mS>)qPS^ z3#J`v_P1X=v}3Jos3vqz%=(sFwdUiN&W=m#IEg@F*6liAPsfcjdln7$CMSEVwtU{{ zzkUAQ)inr4mJ3(mq_vyou!$@aMZttOFSq#{4^9?T$GFWJvklbJetF+4uo$}!i_f+P zJ8yotVkm4U$o5>nX&c`VT|$T%yKED*f4p24RLoGaG1U?DUqf90K&14UEH=U0A1l3s<_Hm)Y>(#-nK7r9Y!k%(Y!r@~ zxoq|76?1E_RqPYeM*G1c|ZS_anE&7D7IYL%Sh!aHfz?!!ml-ZFm# zBEdU-%ZV%3K0C0qihVb{V^$o#di~>V)5|gQ0weoP*z(c2g9`>>HAvE0GbIE*PLvDU^@gPK>thF$?rTI zNnFD-gh&3#dB?A-H%*go1*KW<>f1xnQ>gd;a9B<4$Pww^myb-PKk}rI(EHU{PC4V5 zA@4;~`KxL7@W|OsIWNgfquz0}%x>>;Uer3@scAjBy{C73PwMtAXFj9!J-fXlB~>1d z`uCqbkLrwOpj_LAUU&Rma+`Zc@cfZvxw*A!+|Kx}}C*m%RxRG@GcSQW}Mcj`g?#+lBw%h|wohl)b_ZRkmd3{_6Ps!KK)h?6&L3-Z|OXV%4hR+-17Nu$#c)00P)@l2=Oi{q6A;` zlf&H!zL4eJCEnrWkMa5+3!ie&u7AJIja_waqy~*$b#COF%&PEU9z15~*{9lmA%V{%{+*eE*?3H+9vyi5fI@)w#*%Ymz5grH&?_c<%&+ zc$XAWf-m}iF98AFv!u@5N~F#aPW}XSj(dWi#ujjyCbw;xeYFl|D>;>JvfpfK@*=Mx z{z`oJw{Fp0iTXzJk!v@nB+a51$>^~$LgIb4TXr=*5*RI(KOQ&QgX4Tb>FGj-pl&%8 zsY$d7wy4yrZiNU@oG&_FEb=eL+oOqXfJ6{hMS(GH7Kp_ZKG5voYAWMuax z5G?G)D{gv3{5tNGxZRO19ch3@s@h1t44ni9jIR7NKqEP+;e@1RoCz6aH6qdO!4{RG zpaFzXh-iSwV7AD=7*7KTX&iYX4V8ojhz1EaQXCB+K~lER#5d9a-2<(}$Qcsogo*}$ zLMkCyL?VPE#Kj}YCBh=40lLB@Txcvw+@V3Vkub%cL?dY_PN*ekd4wtwEE&-N1Pgod ziW?e0`9;LP*8ojawTXNg#|eyHUHNH%CUR0^n+A|UFJzR}ghaasTU3gI1`t9aq5&c^ zi$(s$cp5-RX#n!I@}mKwLBfp`M*~QZlr1#zO*BCFKr1nFhD32F6PXk)m5?kV5yBDT z;*sPMVG+^*U11U~G?pap&>-4Km|{<&k+c*i)RMD2LKO*?jA#IYg}r#i4Gp0DBI2$F zh_k7?0c`w!UnaQ(t{jlN0&F}wmpE7xA3yMrxlV#*o^#$EY*8r+-69ksu7QY5ix&A8 zczY`?fNO5tR--xKu*2h(riS3F?vL z5@8W?`2*orn1lWc27qBeI? z3b~Y`)(a*_WXn7n9|?;VVp17l71^q}SaLy{-9qp7N&MY3k);XQs)P}*&`8aYEwi~z zEp5@257{<`eJbkrWZQ&n)f|RwaXZx<>0|Eh9vYp0WoAOQ(8!D)Zt}%36A4U<1QTde zKBOfXAzN_>j(E*TYB{X?)1St) z+&wfpwaTQ!uLhuz$vSLu-v~DH2(b`JhLSWggLC1q82uL|7AT*Ay%@ z<6;~&d&>$FU9TL-AgrmVDM?sE1Y>J^m^cvrt*=GgM{vKza}Se=OeC!H%B631pHpSJ zr8(6jQ?&ISo$b0NOCgJrMqPS<%$X5K%9a=@$I+EhR|fPEHGv6!KDanZX6AgKCs3^S0Dc!UQjOp@gUq)$D)}m5UG?5h8HY z#>CkjGv76NQcz^Rv1VNgz#pB~x@JWoi;_lNq-V{v%0Y;V0#P&~#O{-r99L1lJoD8-Nm#_)q=JKn~N(Gw8f7s9$&z^(i0VUMTp9M z1Q#I!83m|y$GvyWlCTt+Sgh%l0{+k>DP&R7s2iD!A`U`S6o`U}Fq&!C{286CBr!tl z1~0ovB&AHLLLxvH>2#m-gfp4D~ue}JqjL!pv68R3zU<;qMfIZ2q{);m3M4`W@% z``04wBe>tqnVhUmLAuEhT_S6VbmYrV@iv#|;3%YNUw+Wpu0|C#p$*1zu0 zpK@nF@`_)7*Mot0HP4WUwfK+h4rr%|7mKo3`5)f>oAf7rPxQ%s()Xk<{n?Q(4@-Y` zbj|1&t83D~|NUnqqknI&KxqC??G?OCS#do7*}VeUe@La`|A>3<^P|VUlv_LU)!N(_ zWNU;Vk%(=4*4SY$|K%$$rDqmeFW)Mxd1+X6dew_Bk9_{+7hkC!@$`=~AE{FRew_KO ze^Y*iUzeocD*SpRr_AggSyeAq*QRHdzgYWX`j3Vo<{yk4G5VFU>EC^|_T^!@&%9VY zVq|sA$PrP>+?vr7N2WjFizHOu+*hhc*QUQT`qhyy)DS+bx_0!i7e)`ONw0np@2Jt$ zwYfF56J8jJzj`DgFAS@G{*__Rk4(=TS)KbzjbuH1Sk1`X5u;xo`O{k3I{k&(mkIpo zi!Z$Ra=PW@zWn^~^e5!MY+}Z|Qd2v+YC`%?M^@L46w=`@zM4Ds#StUZtKecpBw+mL z5w)YzpM0Y4*PiSX2^uwW^z)-?)1T-a@!!q4usXW>dBwMg|IR<#+qds{x;kuR8|R&` zs5sj(YwOE)?3;gpmX>^HfJOIik>wu!Y9yVN9?uk;n1c%hyz5w)1sKbm(A}0C4eP6{ z84xG~vbOubXj+--LR3*%h(PP&5903%RtsCQ7FB%`v{vdY|DkHI(6MOqAG__!C5Zny zGA53I09({#WO<|<2^1F^QIS+(QZ6M(Dg>_bQXWP(D{`|HBbf_@Qjk>fR_k<6is~dJ z{4c7P?e~+~RMjX{vKD6Utj*o5Q_|>4b0Ry-b|$1~GJ4d86-|0aeq9KsoA@ksP*qaO zv$D0BQ?y-lmpe&CZHtNCYR#DFRcDCxdKtw0ADeKZvtHCN9-XlNmnR$7G5Ta`jnWp8CV|Y-grdeJN!-*ux?57o;t{Pyl2{EYGwPk3 z<-L2^kySz16DH)DqJ7G28Dn0=7nA&*uJa%2HWTSOcOD?#SRPRqh>O5bP3U&Rn9shpU`yC-ffc5deQ*rUz zd=a-W;{I90m9tTie_aAZgeOm`K-&e83;vtmvfR$+l^dsvR*JlxA6V`~=+feU^c$A@ zHRaGAH!c4&lP&lA5&xh6H_Jb?$a2TLZ@IM*|6k`@{<@)-`-|UL?vmq{yUIEj7q_w4 z^8ap@Fca>WcoioX@xI|l(W*3o4$Up6~29t zN#@ zov_^RS?w7Aqlo{}!F>YBrOpTjJCkYo=#Om~7K=;mvTy2GY_g&gA@L8rrE#5+vSLu6=&;sHlE!*Dvn zMS3y>E{ExzqV05qo$mV`;p`%vuuMnD?IWq_bdu(AN7$?9JIpc(AX@i1!n*e!N2ti{ zeiHA~j_@!e&cW|!;3x$q$u^rBI3K4l$xaII;RqNGhbJ2vcLx4@9X3xQan!>2D*i7y z!i9Po0VxpNPX?0N!w#EM5(yDZ($3H$|+&?jd8--3uR~TB0N5 zqda_s_(YgeKV7kt;|MvGpq*q%6PV5fy+S(Lokj=C z7D*>K0`C3}hx41fBUDuj;a+O-1TOE{(g282clh8R76ZlWWx!@5*OgzgsZ zT~Hh3ZPF-~sHFiMNW>#0j3jp-jC*?`3)K-49HDHFNJ4ix!o8)LjU=9AN4WOcG)J&E zA`q56@s5Dk;mjU+pCjz!&L`6&Ts&y?nhZL7Z$u+y8rc>%URuH9knZe(8|j16Cw1D; z{Urx>Q7-Q@jRZb~%BlGzBL%*bHRcvdG(3+}KB*+zB);nE z;;I@0s-z5_dsPihotn82i9snz6j|m@)50DDCScC&YvpM=Ieo8tBp0+KW#<&r+ri1ME-NBSj3FDJ$gbJQ=pe@2mn!JEolf0#H@dSPC~#Z`r-rx1#Y z{v6z4&PLiMwXr3+w4f{dC8>t{0rDbPrxhxYgYHS{g^w88HB{u`w}U1Z0+<;hdZ{{> z*1=y6l&V>{guqM^8v2Dd`%7fV$($sTC&9#XxP4(ZDHSQx+#)oqG=xPN($xK>p$b)n zr9BOZ*i(@{K&~$KkdsoR4##RlB58)OAGh% z!kmkv1Pl?$FI?6uO$+wYw8AuGib=`H)^wxjJ}sQV-F`~!Ne^1R&Ula;+BMBUY&0mH zNXf@3od6!QRinvRQC*F?Alv{F)Z8>Xy|jnZbUoxWGZLI(!xiTw|5T^xz0YatiB8j_ z<3332y-u?~;hsmG!DpNvBU1(2icGK1X)_X?!6`pCG8|6&wHLx3&LF*B@~Dus50kSV z?+h}K4Bct-e1#J6x}468c&F*0aF5+-8V@+bWf@Lq_$5y=1TGRXxGCpxXV_)j?+m$n z>2XA4IzvW((o;(k!oB__nD)S&p6)dJN(4-Me9o{Z;~r@jvmecY&hId85#KRb=n+=!bQz8a5E^yL6H@nG-ucgr3rIp zk(H;Z_k>%oBz=@80aCj&;C;=r$rBIXP9b%XsZ?C?pX{_DbYfiPgt+07`ei)hDUvXF zQ<=LSCXSTCyok%?p@j;eD1X-O3_|6B&_Py)%W2C9zaZ7{JV0IqyJ&@Sr3 zbA|eQT@HMNDA7yRb;Vx}l&V==RMij~`h_p!OAscIza)o=k+?-LYosD&A8lYrJ4k5= zi!!9Ci>Qbr+aVQ}_B0^Uok53_m6H1R#G4|0z(Grh%uRzn;2=*W(J!llO+AG!W!&ow z=}xILxs&1<1Hl_UX_5qI!1#&N>G~$}K<27WIt`#Bc!T!R$ya)VI}H~xdq6J4q|(AK zxI9u5ZYz}aU?|;5)$0TZ z3cBL)339{#tj{W6U@=;NRKs)&{HH9*ewct>qHh2O-WLL z2o1?dbPOrmCv{9Bo0p_5g%ceO1JtJgw;K)1=@xN8;`wF1pj#uyVe?uTJ<^WuZxX3j z&~^fad&9Z#mVd-2BwPDnB|zn}qutN}18 zvlB5H;1}SeX=G`kUUH40f>OO%ae=cy@;H7m3Z7-*B`K(=(4EfBh)%ZEUHt*JPK7&J z!9i*20gwiLTDE|O=Kv;7Es5ov#Xelrzlgjk63t72E|fCT7i9o8-B~0C=q9na18IOE z&k4hPxbC%V;E)f92ud>n@F=0cmu!JB(DxTN4&?2oCISpyaA7=6pm6jMIL*y3IL+rG zwGxz9Byb9UAvjubA^;`37$s&VV7jqS0-S1D3Z$ap+S8Z;Las=rTIawz-GaKNqF=m zAC-hAX*9dZZq1dWVYrZ@JyHoNjOe^PBj9M{7uNcx;K4QwbVFP)FyW=jDg!Df$JV}NPy zSNUUzL{vl%I=t!mE-NO>C`(}2=-Z1wES;udIQ<|ALtrIX*69NHprU~>T(Z(;IYWI7 zg*sR+`?JCWIl~@roU;vDR#M=SAQ!hM-Ps8|(486206ujPf}l{eX_iwSKqHH^uP+Px zOA4cbYEYt`;r>4FzR6ag;k)!OTuuXGyF?|EPcU4 zk^qn$b_sG)=n48~1#ZNFj@?odxX6HmjHd|{jvfM>d0fb&oKOYgq-Fwn@MCPUI1#)l zh*%(x0cNCB)L#+_S`cVOd^QRD;<^#2ubfC7LJtFwN=P!`hYr{Wa)x{9Pw@h+g8Nk2 z3kDMcw=@V+QRM)6+>*J6zzD2UXb<3{AR$eo2z>xay|2$16et61&2ZHUKZ22Jp9ES` z%?c_gFr4s^tE{ge7+3)-qW}nUig&itfPHr^_c#q9rT+3{JO~UN_JRv3nXYuiCz#g& zOl3%BMPwm7^q0yfk=0KCU>QqOkvc0;g3=QLcbG0GUFWFzM(C#&>%1Nlx9$ zHqlHv?I;gQg@8O;MuDntNFTZBF+!lo?je0xbSLR4*+ipn`TE1U>QBf=>g1(ar2DC+ zALbL5H5uP_mS zBP;Q}tozv#+)FLX7UQS25A^2mM+rdHuzt1?b#l$CoMh8xMfow(DIx!d!Js!}w&yZ#j+a54k zEY!$7+!y;EmK)5|ZZT>iXl4Jzu!%uDyvPs&mO6UF0k%olr%zK2C=i{p!n%J*4)hE`;ebrVz31z^!o#pY-@Ct^3$)qt2FEG=K)e=jx!yr&;2?bQtlMAC@KLbU?2FIB0;EF~O zd4`8!O$`t8f*1wndnn$A3^JtC5#gjKhI?mFJ)iLiK_YmV@raD21KjTjmZ`qM@Syv+ zrDfcG*fpSv7)DKZCJCg~AVxNHTG?t)Z7HJT5re5P7EvJl*w>Mt*pvmJd!=7UWhjus zLSFPM>Q6eF3*GkFY&C#HJMmIvc9MfNP!?|&OD$$4#3bN_<bfKteDP|37I%R+vQ``5QmkZVQWi_GOS6G~)KW|VA)lt~X95W+3$zGG6l1L* zAM6+s+)J&|pc)ozLtqdP0Q*%)?vC zJ@>SR1^~XH7~(PNNj0!Dc9wyk`I?yEhZ0~l@T@3X&=p|U{je5rzV@1?>~w;dsG1YN z5SsN{UIlzIj|ub!#f6i^vL+TXlRAKOu01iX=?j+~lG42eAmwfk3dNn+D1T`}t@F zKZqHm2>8!%(V!T+!x=uTdZIDVE>u43!-$7k1LI=X%%;he6%^5BW75pl#G2=MN(;Y~ zjf{(EWjAtC*b3=o3(ms)X}D-zxV7Ptx(PV#C7lJtrVEazTLiPUULeUhsUf!snL>kj zMU^p=en>u8oZa^e9|%UkT+-Kh0BT9Sq%YFSE&?(T0lNU`#k|u?#Mmu>o0hlHXu<3v z=}*{~HDO6w*v&!7jHF!1U?v+K$VU5xlMJ{Lbc}q%&q-88Hpo-ijw!zgS%^JW1S*j_ zh&_Lio%yeY2sAKj6QYmak0ij;-Mb?4~C<_RRF;A?tf>h{&65LCz{B#{) z07|)O04y+EgNG$poI02=9aV77s2SJ@H;?f}C)SOzhADd0z$Vs%77A-C6)2?0d9mz=9f zD5gP|r0NOQmeN5|aMupGvv0ZK^dvd`E@zR`Euc`OBie_D2Fd_X_QIs(7bYAI(I~_$ zij_O;>4iB@)DVHwuvv)ljX zn+<~6;n{RRCB{@`&Ane4OvTvhNTKT)(so84r_D{J1MV9o4W43_RuM#1M5rW=gj28Q;!k&V1A9nZ+M>dlg?SmnL z7)ek5eX%35T2Z5sSS2-Jp3*LoMGo1|ijW?I-}@Z?>^f}i8s=}ehZ?ayDa0tL1qq6x zB#j|iOY8Ck_74M*-lxuGm>yU*z6NK~aj3bwFZ7QqYbr&~qkjlObw5rH=r4w-42C1z ze;5W=XMc?DV!O7~8VV7yDJA7w*v83_jewx+G?A|yn)xNUREW+XKombG2~gunBIQcb z=W$6YJWFR&XGq8vv7!u7769BPX9wtboLQjdrDpUzk%Q^ZBa&RO4YMT5I$Gglf$!x5 zE&wb+nOs~aHbn4=4VxJHu!1kL{&Lnrt;u087B^~3z_%d;f(QzBYT10HF6?|(!9868 zUkp?pS@hW##oz@ZQ-D;mVw+e&9ld z6z-;&uuB0HGVp*(Tx@%jC44HJ6t8sxGsJQX95aCVpi%bD)#(Um6uM6e@HWyt zU>T>Ao)I`qeX>P8kV6ORXaS%`9Y6pG%0|ARCs8*<=wj{z9NW}(H_%zmeTa$M3DN~; zi+E4~`WK0Y0U8qs7%mdqq|_p*2rT4wRS2+rguJLGjlqnHm<<7!UnN2S9t6Dr=(=hH zazTwyK>bUa-=%po1jL1XvV|>pS8%RRD1lMvcJNjSj|94%K)}ApR3UJO*5TIJ=mY{j zM*My3n?Hw4fQvRkywdc_ybf{}um-MFEheQ%<~)H-6-qm>fmKxnTwjAv1gErxzR*9e zOskYSOKho`Jfi79Eh6N*glx~!!iE-p_BpJu`mA|z{QgRpf!z0$V+6c02e}b3l#jH?7h#A9Os$mnW?aqvdRisX(=p)6|zHQkPNa! z1*MXdrcz4VHLXNp91sm2bP(V}4M1Q7w1GJ^usj$|p!*b{25z;&2rsXnBNy)tbIPHMrkVB5f_;?kjG-`!ylIz*ksYWjw6X=7k1fXBx%NNZ$jzswtg_B@^5+`$&xzu8cFCOxA#9A5!k z>=@q1L~ho3dO)a^{*&~ve3E+?=^s#`AaBI?9s|Ze1dO)3s-N=??^=DQVW0GIhT#17 zw}Bb88-x>zmKG4`Htgg!f>FrdWIEWLmjgn&oGuxyOGc}Zcgbj7GFq36R#4WG(Yj=` zf((GyOGfLG(JK4`I4v2i;C!&=lF_H%h)_M zA1@iLOGfLG(Yj=`E*Y(yl}Sb9P;<#>T{2pij8^#-OGYb4s!K-elFvzOxg;2T3Xq5!}DzC~bgg_dMRy}Io zJ;rElJ%)}kTBW%jF+`4BthgGTWV1q7JjrH-T^Vdv`Ig7o ztSzPYIFl7Obz~O~nXH#+n;c0Ptvb^OgS8I+=5hAwLD4&ly9#CX7ZLQdss&8D1+FUW#^9<-#>C>b z$W^Vl0blPpR~6>tX|5^``zLs+vi{(K21imam3NQxR9nv+e0DT>%2UNPYJsPUcX;qr zzjHQEmDhr&di_kE>ho|WX~{8;Dz51#IjT5=oxxG%q&7IJQYBAvR9kBAaenF#B!S`_ z)$&s>(PDXx@KklmKjNj<{C%2}x}V~ea|R<7y5%uODm=_NBbB~<#7M=XDfz*ii3<7n z6chEOGnuGly~srUF*Q44qS~h?Sg1sNKF&f77cJ?*k4UIE`=3ccopz+q>VkK~K!tjo zRYwd|Ik&HJ>EZ{yGZ?6J;3EdA{!K>=)cp*Rs09M*a85vd@dN>t2^$1dh^MCrsE|^N z9m?e0AfQ4cJtCln!`*QLDsmF4T|FY8GPKZgb;WB5sF%(lpce4w76_=27K4Ba#qu-( zwSw{o3-34qRS%(41XP^yPmoVNOorVVW#-Bwqg+h6ZW(v1*PBWD!dqgwEsi^;hIlUA<@hN)gOJ~wc z$$F7q`eSN&L@%{RPf$yrJwYv%x_W|H$`SdQywYhJw9?gMv{LBA=$GlYFRfH2t+Y}d z?G9j|3UdD+%J&cX>>w=j|=A{9pCj;QR@dkYy7K$ zK428^NKfzXnk5`}58ldv8Hh~=oYVaVkDqC>uFs859k}m=d{n3>x#WTqw65}fzr;%? z^f9mS79QAXj%#?jMLD>H7dlJT`hwu|-YSg(FY=*Jbab+AxCW{2s%J|VKbcAWF^(+V zh9>BHP0^6Ifek&~&G9UUJP-I7QM!^p#VftvkxSECPtfhmg%ipyg)l7s@MWsFW%@(z zA{~m`(%K7VrK(VAkLm8MC%b^f@4mj<`Hy5kYZ`sU2 zlPX{Kx)UJWn>bL{2T0F${PFq6g2%webW0gA6;&&Ay>I}tLZroceII4kp6}2DleBK= zJ~Fc_ag+rHhyR<|s^!otcq}mc*j~QWn{M6HuM1FFmQe|2r;4Pc{}h)o-9h;?VUC0d zE>jbiESjv3{sy(t%g9=xyY>5$CS<)`eByRWEKGWO3Md#`CubS|XjGb2_{m1y8H;O~yOo7PG2#|&6#w>k{Pu!Rk zanw&f18UU3f)WRCJ!>B7n}aI~03l~V2hk~}38vF2Mztc*p`JJR{Y*2Q{c+_W8@zWy zVhX>X01lLNC@qD?(tbmhmcDX@3qPHr1~VmKP4QP>|81SDa(zAUR}VoWaYujX6R@f- zB^eGv>blNWE&};dyqsHt& z8E43C_C*=G1(l&mi7$Ios5c4R!ixy?Pdu4cU+7Fw0-!%=L@W16-NTJ&1%*nX3NR$a zgQQ{bNP4ia&{(Alf0N}jcjm>PQb~I z1g)JmwYpwaLpn!5$91u~t+XhBX=dk$_VL{cs5)6G94TOy9#<(1-J?PMPH`E$gmhJ* zf`KiwsdqoVJI1f5sXLtp^n)MLWCgcO9LBfC@LNZy7<{HY68+lU=td#oBnGBLaFKiv z9d(pxqZhv9_|My5YN*^n=y?H36XcV+!hJk{bRV`y@%IX!jFsh z71~wo)+t^ik3f3LMn5WitsDrej*8ds8DlgO?&5f-P8eQV(|hh?n9!42At>ewDM{^c z37OAP;HZPZZ>U~{wC^Z>7i@j@DZ7I}Lp)NQ057}!tqr{9>|BO^gEE+}vqsl6JJ4y@ zs4|6z6080gM2GXoEP0Dh9HDe_(Knuz9nm=5ok3)H=RDMx1s56cNCte+bsZ049Yt7t z@mvomsv9+~L(>8Wf4o6-_*kDi0)0DtkQ7on)TlzNXGy&{utPqIzdtQa77Z;ibnxG30^x1(6gX{W8LS`KL|xk3Y_&oS^5rz|IiER9{%Pm z5i91~@VGHge4xg$Yqr^7~pu%;m$Su1s9|0#RqcOCc(J14dlxHa|Z zgGpY6!+Q}G_WdF3v8K9y>sslIep3>2kLwe$VS05W1OFrEqXcS!gI%1}UHytF&dk#SZEC^9tOf>FKv;I}WKD@c zzykXo=~Q-_=Ih+Y4O`%Zdz?6tx3)tbUa?e+Y5P^Lw~aa!2oUs_Xebje@^VJbbP-=D z{Wci|gyC;JuZ4fg(Cd|*TqUs>N$sH9Wd5J@@D~iR;V+MMprFvyj*=Q$4sOm(s1h5C_>l@J^>5XVz zevt>}b%{?BBKAG&h}pjjz`iwj35|lhx21wv!q$TS6yQt}GL&2nO{pA%eQ5VNrHV0^ z_@1nXz|v4On{6{V4xlMVpvti>^@e`%_A@&#$-l9KUkR z18%aNr>5ng$J&VU;sLfMA!UFbowNllEWUb&tB2GF!>!=Y+|>nHxTdFc{~Z!O(aNG0 z8vu(01@_d%oK^M!e{a0(C+SMa&69TpnnUe~d{~DCNjS)>Dao>|*3z^;<~}~uf}1%G zezNm$2fUDVHYg4ZyAPh)L9}VI!T`4@NF<$ts1-H>A>pb{i~8qUAb7))1Q%RUbFv8) zD8U5jbg<5@@{$k;(VmAN0VX$;d}k!UHsHoFZtLMb9&)S!pD&OJP)88AqDoRpX~atC zCZQsr*;dzogyh*4Omak)$qm_8Kod;~7juA$C1URcTGE3_R^&xVr7TGqCvJX`Zl}d~ zdz>TB8t)h52cO+gV=90Q@D62X+CWtlfqGbc^xNM)s3TT@koxJ)m*v)j&+ayvz>8hM zK>@f11p+T0X61NeO`-eHrJ}q(uM}v?XGNj7f!R`y>aPZY0Hq#e{UpI=oOK%7S6}1- zc~vAtn=}$5jDYwYiNDM6+-P0`CigqTenuDYE|mp2Px?hjAL+3voIObXu&?vep(OQ zuoMPtU|K8uq`sPit5zAkz&U{PQ?N+`YCeetfqH?Xh_V)moo&XE==1yIRek_YA8poT z!<=(a=&bY#ZSN>KCeB>BZ37%@ekh@yN>hC=%(m8a_tK2I% z&aGPddWV%yID+4{fH5GAZrnl=mR9}8RX8d^XzXA`5~3iGoxarNJEVM~l~1j$R+ zT;(P44PfEnE=y3!zy5qf*GE*ijbpoXS6&5f1USAxbHFiUAX-oBUO{PuJ?QpBWk7Fk zr2)`{2lQiVu_da*VhW@w_FI^jDG}o-z>yB@7gbpesFZ!KFcUYw$ZzJ=_RWnF71wyb z+P?qk&LHf%ybG3`7F)D|swnI9u-g5{f4twQo+@Al@O?Shy#MK)K_RLF^B*8YuMQJZ z0S}A8_SS})^`SIHc_pt@sKXbPQ3x2E7C?(vu@Qqo4~nwi$NXHRtF*7V$OH0Hj>^8Y z`;2d(eY5;7!?QDd2@qVSb(sM=^^(U`zTi)^bx6X6h65sgWNug5i%c2j4w;DSRU%`L zz@lkM0LIWAo2@fmiblDOVFweo)K%c=O*c&3r-PA@XA<_uG+HPRsAXcg9jOQ>ps1}n zPAaB3xT=rQ3n<{R90!K?}Z7Ny&tol`9`&kOc}yTA|oum>$~{{0Iat(0it0w z9odn9)~-lt&0MBi?v^Psrq~l08r5hgrr7L&bo{ps+;``%MEC7tpG7-TeB9d!}}{uI>zS5D#G^f z#>=Fn==2@Ub}aAI5nM&`oJt0)CBJkPdlU+%$~~d6ufh?wL(H={oBLg;TY?Fws!+*e zRTQE^$9=fQ>%tV;ADBpp3p#0142WT^BH&KHqXs&!VfYr+en1_@pk>XxT3i*b;#eP) z$XW5U+Mo|Tvb8G$5708J0FLd3vgEB1-=f_h0qul&H;#J6927UcTVJr_gi>G)PI(bd zqK;tBt|(^Je7e}wZWPoyyFw{Ebp*jznAQ?a*H(-2wYt@-sI?|GrubfGwxg!Kn){%5 zM9v`{*r#zPFq0j@FNE8ICYu2`0s(2`1wWCS3b2_*yX*pKQ9uP&tGY!S9A%;#umR&M z@9{| zClrF)3>u7)nSC5j&+&${##S;HwjX3zGMTaT!NKZEX9Jws=Q&-bg%#urf^2~r;{V8< zMjS56V>AAIXKzbmxrXGhs+wmAu+nIXa6S^RK}5<596IE##4J`?GF07EX1!)HWyK4z zB7`KXQsM8nxm=34*54pK#vLFaASX|25E6DNwx=M2SfWLNYRQ+Z(+V@O&I;6OmQQR! z-Qs7MPXKKjgoz6S7Ym!q<$iB9-@x$384re*&6i0@(aAg7&RB)1Bk+g32~ZaRQSgEk zFmVnr^G;z8I*5x+6ajffO8nMhSch!vUYY52(XB&`?{eW>58}ILHUg@vqyo*`g0UrL`+!*`{Stg;zim z$Rag2$5bfV&4R75F6&ySfCdydy3<^sF`9n0{uO8lt%`VnhF!tNzH0`Ux$v@cb9ia{OL3b8^#dEO@ z&Cr%7HV61>VJyM@H{NdW?z9fcj%7_s8mpGU#<|bC$NE0y=buWD>($$&Kg_E zT)26kz)O8+ET9xS_m*&w=XBW=R3SnI=>?$b5%x%fvEYpY!|J@Z$@-dV>I~DSnS%0+ zB5jKtfU?rdj3e5n{J|^$KpAu z76TC#=YVjKVU?pU=((eGDcBO*u*4^qS$JR3Y-AFT&ogY30s&1m#gF(-RjJ*y+!iP9 zUSswDzc?xd`E?1I1WTiLdzIHb2sIh#qCkQ!0)Br^do|X z?x$$A^Eti6>Fjh2;NnPYwIs&WUt=jfNVir2eBECFSy%4uuaYiJ#Iu0Datx{?@E>^z z7BBSnXgB{O@IbQpQ+iWOZM83f$es^LcBv4_HOiz`9Y$IB?G*B0*ajqkS_DU@xAQl8 zKjh!^t1hSM4_zKvcvNMSBBf0#*7Z$3!4H+I8uwyJWR>%%ZRMw2ENuM&_c2tguKYQP zN$9bunHb~-@yQnEgvCMIgt^1<%5vqXYma7GwwQRl*K{Ty-H))3E;|=kk9Y1Hu`*$) zRSS>l!YMVi=C*vK8n-+HgQidXW@PBX&s46wZUgET_)A?vj4xSB?n!u>k?e13q zVmEmd97-#xtU29MAd%Qd;xp-dS^TC^2vdX0m=t-tE(cu5S+WZ{G;DtF^X4kG6wBP9 z5S25reG%#vaA%j0ttz6f%E|ZWaaU`4kAF^RYM@Y&DA^%-zQ+k_RvSUm;7Dqq2e1Qy zs83|vojC5L&#bvN#+>y(YK{yi;+dV5tGr8p+#7@84w0VrB!f3SAUxzg@j5%i+MI52 zU^(4}O$upoTr#76`4Qse^+)3jA@g4XNr(6U&ztrQ)~WD}a$C7x5O*u=-G#X=P3j-Rr&vG$Y$p0VX0 zxlBQ;^5sN9{e zaN2p7M^Wi^#WaQ@|4OLu-C(ErvigUCqLTs?SNHka8B2P`vRcujVLp2w475ru#T%Ox zVsIPGNHZHPzKd#J;PY&1T_sy zdLYh4=`>Fu!{Vqp$C@K~i6&>-yUM%t$A|0StV1-WX~u9#e}}nezXBZsd8eD4>`iaN z@q|oxvjouI-$k~2^oyGX65oFfY}|bCkHlbdPSI;%1x^$(ys-9`KC~^>N-wT)7%xU;Y6zE|ke)7Lc7kNY<#(D$Ue3koda0G` z*?<_V>MRFXW|o9HtOZL#-I7oTC|VNgmV`Q??2=HoB-F8FE(vu@LLKIvC82Ifs9O^1 zmV~+`p-#&jHXFnHEeUl?LLD;!8|{)%w9d2^K^VksQZ^D)cxbI)CvK zE~s0%4ErF{?eOrBO!wL$neNp`WIB0XlIh@f&B=6<-dZx96u9GLItmLi9mKjI)BX4n znGQ#UmP{w7%=aYI!Q6sOhu1#e&2>bDrP9IlEB=h{Qr4*CoJt4P?-7-*hQ)OjkM01C zma|!Od+Gh-96Bmg52-JFIzGD!v*4yX=g!%Ghuk?Tcf^~6%fE&TunwQ_=04yL`gM^r z$E(4an8eXva=A1dGdXG4Bv@_gEPjlvQ13Bc(edjo5j=;>PICH#xf-`r1 zfiw3LMiDYxaON&7a^_y2bLJR~;LI^of-^@05<02n%r(95v%I&Qx$n!F)6Wgw(Gh1( zz8sXU;LE)aC&>dw2xlul;ewmge;X4@aOE~g2+iz>D+hsTkt@fWC%JND5L`J5opa^n zvmN8gX|g-cmBVxwTse4iI{0Nje#DitY`Jn!%#OHn;5WE($S-lvoMp;g0oP##EFk%< z{23Q@fG57dltUHeI8(0fOwXjr9n?4f2uW_Q_{~X*oGiQ{EjUMi4xz?frdsbFQsW?f z9Z}<`?-4ByvivGOw){{JX>k|$LT4^g;%M3tB@TvNP~vFn0wsb8fveN$nf$kd z=I$S1zwK53aFY8*9kU@#I7f#L;k;d@TJIim-e7Ybao(sefr4AU8%*^9eyB*k9P-^R z@P*!7|ExN;|$Zs!-7Za;zL#w-bz8%;XSa(jKwa%1d*I+_CV=oK9(SdPPO4feQsja&tI zMxL!qLRUXDoZVRZyv-7Z1Vt-op>tnHYPe!yYc9YpGrCSDEbOjy>F_%86TM`2EnACx zC+X+6Agk9U?uQh?GnG@m*?agQOn0=v$RquL(@4mBjcRGK-f=kG?|kw}FQYl?BdB@z zi(l!j34TCJMuY=v96tz{ZlIb<4t@p)KU2D{(3MUaxU6xWTFrvL^BnbzRuzM)rGH5( zyFdYtXIL}IJ*mkn?R}B+N^&df;aWg_*L$^2p28Wh%?#(f=?Kw@J-j^;T2pjRG(<_L z&*5lDI{JM&$bcR?k(T%`@;l@UxG4i4pj|epdZGL9c5k|?A47A2yzL1pp?t5XMBG;9 z8JOkSUWonDIcqo!BBe+Iya1~nu7ofi3;FORxda#!Xj#yY$$^;>P=HRb_66&FM%A)4 z>uZm2qoK`?vsc5wN3^nMd6B`)D}YIb(A+^@^+G6vx+T{YCVY+To+brur(J4{S_-vd zQ0&j?EqofMw<`T7X}9Wm`fljL>4TJ+I9)4gUL!2iQ^BDs>|-YY!pArhM1|EK&caI< z?*V-&DiSMIENrp4^bM0XTsL_d9t2cTuTU%IM6Q{0C~x5G(LytvdYu=vP>)#(n^bGg zIR)$(E_l=1b^L%7Z>h)>XI24($c80WVFJX?nCEi6W#L2Y9=p`KbmKY)C4Ggpt6;@N zo`8SkgbHW|NCJ;FTGls|Z3PtYJnZT-r&AvVOq!0w@Ko(E%4#JQy6O|)JSSn9+bqtw z;3Kfa<`$Q=u}b(8*94psom&Q3Dl^ zhsxLw_=<{e(Iv;{=Zewm;4{E7?y!RF^S~dGE7&kXX4p-jYquYn7D0LmsEpS>e~Y`Y zI|1VW=5_#kgzX({O6U~wNVpfe&D1(;fL-(886dy__5lp($6By@g%?DObjKRX){8+j zUO6*}2KheXa$O1>1~mq8zmiwl__y_x%NN51tNV8 zEYAH1rg4>;IdNz?+HRT-aXk`K|26X?f@xeN2lg+z6{9#f9p6GO`dBaxy478U-4>ga z_vgVha0k}0ntb))xlb+;OhX{bca6HbIs05;!1U*Q@~U-q<{bsoVA?JQ(?D0`Q7{eO zj$j(t7ag4Z@)^N2ko8>*rh(Nhf@y$i!x2=_inC+Y*UpB^rurGjgK3-t%5Fa;B2EJM zBC1iS!-eA_M~BL!sZ#^P{jE{5+_lF2A}shvwk>*oK+hh_f6N z>^gD!O-riwF>^cOZZP3b#ogfD;^(}>yH?+McocVoAHa$~4h+g&RD8A2mzss4m6uIkbjm?%SJc8K3@iMSiq4HP5zw=Oi}NQp*qNZM_wc|qoQ_i`t6 z+IW8+e*=%tGvaSx7LE8DHMYBx@i+EydFb_!&*h(QT}t=S-{_;dObm{1l0DtnJCD6l zb1k;Ph{J&?v9GO|?`o9jkE$Rsmewwpq8~h(<_X#^33|J@|JykzzpOXmBaP=P3US9H za-eWBkH~>})GlEt5JsbuGaRe?*fLQb;T#Vk6ZEN1MdtYSnUOgtVlgtuUr=Xm7Fr!g z^r$_1GCId|C!%x2BvRG{S-{Xsu>}s8>M!b)aP5D(NuLq1MA5y7(18;%$~Iwp&apK! z@35~%gbw|7U*$SV{83i zXO}zAbGfLZL}thf(4c?}J5s}fFZO(Ed1%- zzCs@n%Ry+1{%s);rWHAmOu3F>0{+x`rs3Cw4YF?9-2xwiLJ(BmPwyqa!oCS2GF4y_ z>&SUAy(muFn;B}6@_CU-;stlG8VA#Fmp z-ckz<)puUuZU-ZtGBkYS4w@uw`%tK(I+ryUhB1~bg{?r_X zFqS4ef?a?pz*oVR1KwJv?~5&M$`M&b&`y`D&KD6%u-9VarkCTEM&^Rd7|k&My65nW zKAQIplimgwV?ed1zlzo|fl*YFwo{`O4z{hh#wUN*4glSl5JiQ%C|2jYXhZuq@+y4t ze-tQaf)gXQBYn}fAU=%gR@(sx+p#6=kb8qcl`RGskr#;zGpUP!r)*l*9A-`PD(ii? zjfs1WF0jg=(R~z~YAnRqa&4!zc(I)|C6gV&?BEd1McRTkf z=VFW8Ca7SsnxhjlSs7^;7OzCq16A6Lv!G-$2iEro)M6^z(EVO%F8&J6g*9Yj{z zro$|xewVd>J1oNCIId@2RG#Hy9`AZaFLt}Z=YYy0Jw46T z$5SMXa*K0-lKUJOBf88SSTO>JuWOAL5;Fw?Op49JF3$j{%cs%*8@_P^oo!;2P(PCF zuWYlUc$fa5&!bIP{?Roe55`NSV`qawS?uNvjdPwLnBBjN4z+WkMWfb>)*c`huDj3;C6R3s~Uh8 zGIqQ74BElGu1NSaKrBzqW6O7m3z4Ho6T!xmK2w{Iub>!(jC&NWj34-Kx&bH?)@tDX`gi8IN^%BF~dA`k);w^AIUdadegfm%7WE`SV^)jWTLbbyEC z&2+jGFoQ1~y?23Dh+a}+S%F|e}! zB^)QoMtx9nO#$h?`YM4|0Xzsi?^h3s&rrMthYA10@isi_52Jui7olx(N3>7-%JpL8 zJ?a*jD<>y{v+{OVA52>T*7MD66yit)>{Y?J3vBO}(>wN#N8G+IaNAT}xXL+l_X)!_ z8CP-@6u*hC7xkj#NBu4f?xS`ssup>(fdR!MSxtN$UZ)Z}e2%Cr5mlx|BM#?^(&#M5 zmVYxxG=xKB8|-+3pXuQPL~?+=ZP1fvodZ34jXn=eJpxJ0qDO(KLVh80k`qJ)l|XU) zH+*BANGTjOJ0xF4f%zu*_g(sfK94pba?^Kv5HVDo+%zs@-(okTt0$sVW%~}|)wIOT z6`wIT-9NNc1ceH1lA9?~ut7kRh_{2Lz*Ov~$&aGAN#3(iBaB3_4Mtx8YeX7AO@L>1 zB7=s?srRHRH2F=3s2!>FIJPCo@^#CDw4V{vggHxAzznf)OuHJ%YBeG0H%63M6djZ} z3#cRzeZs2EkA)K3O=pkBIVA+_-wtZRZs$K}Ms5BS5NbSyjaB{w`#|utZ}bjOZ$za- zZyyUF$YA(JZ^GHD02j~^2k;najcD-)UbQEbR zH|6Y@0cz83`pKCxNQ<8{<>wpRupTdgEE7+zKc2Tl?B(gPE!%F|@+JO-dNK$uVx3Wz z+20cAf)2HlZtEH3mCPZ*4US6a1kxmFV zB)^86;5qHCK|GMo7^suXL~0w<~a4}4Fs}`rv$B}5%$WLxDcEaH`YHr;wYvNr+)LFGheh5b9>p7|H zcAE>t6dHG^A5mNQJ-JsR!8HF>#ZzVg&={ll57;LC>H^;wo-It$3E^d0awigswVi4s z1kF}o?nuWiA%EKhqq_*PjpAounb-n2k%Cn5RPUcixFNUXHvcj1UC1@EZ(v0(|AaM@ z6@O@igyAax>8qrhx6wOj@Q@dv6oKVh;DGF}##NSd7X0aGJYs#PNOoTVyn+UpjxZD! z$OQ^uTCrbb@xVh?7;eM^LnAc6#0Y{$`AG$Ysf975ANxt{4rK49D}uv5HOSxNrZsvA z9FggTdxN`zM3ElR(%3!HH(=?gi6EmSA0btQoD&Z7eM>k3bdzi$0N1G-U_7mF@Gu3u z5-sxYK~=s6zDcz^vngoOxA}v#v7MmFYG<23TIedh66FA>bM+oeZelVPHU^qhKRN(1 zpC*)lm{L`w5j(Iu4(jBd^>J|TAp>bMH)xG2!W&^U2&pNX4a5Ixz0KC3!W%#ic1D(Zzn3O4{ zp^>Cnx&4lC62l|L3yUVYNKje~A;S)U5!&LFYusGphLyYwIPHuTSH!9g6pY2TY{=u z^H@FZ5p^#qBB;DHs$d8d^p3INnc+gnaAwp#$1dQ!laJUY7GMwlQ;~J}fps#X@(}}% z9*GA}8*Z-n6IM;uYL>xiK3L^H#$Ce-rQ}{Nv}_HBth6}`c!XOhFA;ey59Gm;IcAjy zc1{gV@dt5dKqk>kX4ncK0x>bx4aNXU2yweGyMbE(jx5y803dKTa2m*lfAg^*-W*X9 zMfSxrfTK|9OB!s172<`0j2V%HL)WmM-nGIAQ{NZ148TOb0MMTHMNfgCNWU%QASeeS ze6)n+-Z%~Ad-EE-1VAekeclvL(JIjxG0p;CVQ^||B8yT~EbJjpUf7!rF}5jU5MJ@T z=0(Gw5bSQ+Z1ON6VZjfOM)(>KB+=jua9>T{<`2>aaEk^C#koJ}Zwy2+R0TxJ zCP4J`mGlb+aPXVID+uArL)!0!8F4?qb*G=(OeCZ8FjxYpN;4agg>@L)cnGEan}Dz~ z#KcLPsQV~Z!JE3gBU1=6IF#B9OFj~=u`Io z6g8&lqH_P_OsRRSfHxr%+xbNQ*rtkMar(v3@XRnE0!L%*u^owh3MOTY+$2^gln(qS z#?_FD#V-Fb0A0*3aJeCo1&}O>F57}GVa}4@y5zSm`K?QSD=2AI1ga5F6Rn0>KDy~GAyuw zm%twT>4OJtfMZb;@F@i}On&+6I5r3E5XlHg1d`+2T{vi@U%)PSN?RDHc!gu!06&5d zP!mGX!89Px#YNdy8W-0wY4r8u1kjNlHs~!xN6FTR=8xiE*Y*% zhAZ4R%=Sx$>yqILN?bBrImZ9j&2YWc$!1>I)0*)i%k?rAwqUtJfjrG}g%CI-HuWi% z>pw$dJHc_)`{@+N6>NSs#}#U6%W-{=*W^Y@a+I1V#}zYh%W;LwI_J2;Qhda5l}x(8 zZzbI#zm*(wh`hmXC6@DJ_^s3~_^p(^z;C@s<#-m{RtTl1xvlVTlikjLrW!IQtqx9g zqqq2^3E3?^C54jhX>Kd|EpS_ZC!Z`wKY3N zZGA=i`U15TuJL22tq@0{w(4{i#8&Fm5?kL2V(X<-#8!fJ7s)0ACb9JmOtxfwNNl~r z*C4hccXc+g6$a~^*lI5ZvGp81b3|+%No+mOog-o^?BP%?#ZmBVVyk{)CyA}xTO_qU z`8ZPRQ$cE7X90zq*OFQ7P)Oz6zQtLk>!S#sJN)7QzJ4RR`jE*AQ}k>mD=zLZS^qP2hbeHxWPMpCD>Tg` zCMzv1WPMV`X(lU~FECl9Pg4D0vcmIx#ALmCCX-cnJFs4A9G}gC*#(L=#jg|3hX{?*%2_XY<*=XV+jr9s&gT{*B z>)A9`Cu~b&eT&x@XsnFWBO2=*&L*(J5X2?u3<4`R zTLLS@(w4vqJMj^L6`E{IV1=c6jKB(?@DYL48ZMAmNxMj1g)154RXlTKb;6l^9C?*` zLSBWROe!nF_aH7_JmRg=(n?ArWt`@% zlKBE}RkkG658kRKY0y>6TfKTFZ&e34vVgZLweu|6Dm8yBZ8bYaTh&i)k+wRg&YnSA zC9SkoUABU>dX*LiY4xojtzJ4sT4nrN(&{F|Drxl%`h)xrNvl`*8l+V;Y0oCD!b+W! zR^Q_F1=1?xbVOPmL~A9jp6BinX%*V=5ouMIvNK7mI*ZZWa&eDHtCFR45nCXvKKVGp z>Qg~jWpQ+Y41G#*fPZ@?&&pbYaKeFGWztF?lDE0UORnrD3P*UIYZBa{D!oUPVD9Cb>323W!YKp($Ms(-833UG?*;7>z-wJW_v z{u?kWsq|AHJk{H)bQK3+;XT!paNCH(^kUJe2UuAE0yp00J8jdwNhz$d)9{clxoxUR z^Z})8lZs}{QXnIuqv`?Rg+!5xA4O`iN7S{8oBt%g8-7r>0W1Pu>xN2`jm0~BeK)@V z5b_qnoA_5CfnbI3Kkw>=zh*97qyuhQtpu=>*|UNkk^{J=`5IaWX-K<%kNyUH6{1M= zq;LucR^o8X9kMEH*KX&pcm|qQv#Y?*jOyY`)YI3!!WnAz?_9}2MJ*KP?B?# z^hzcuvMhSpsZ!k8#M=jd^ncXz@{1p zt4Wd}g)qx5fMIYZ-+*h2qZM>kbromnyr$*SzAM$dv30pyv&Ey9JOg6XNo6fu9k+X9_!zEi_xyM0k3RH}EBL z8v@YY+s3CV69qD$2dgWc4ZOJeJZI8(@wCSos-O%ozuT1kevJOJBGU&}$|D}W8{5MuFmT-dJ{1;Z#`EncUGs~}-srV@uD{nqv?L!iBExXO4 z$3h5znpLlhlR#7Hc^1oh2=E$eFN(l7f(S+Z&EOb>9-yQ zzM(E{;2Xh~*oGxOxy-`*3J^j`9=4g}`-2t;AbvC}e#CdGO6?9q-&{K$`sPw^9~5wA z^qakWl}Bn%cEE)?Okwt8J=drD%k+M!GW`gpru`J*cRr`L@aCT$M#L$PN5nw~{!~O9K;MaoI2RTo;`}tc z_FIi9aQW!@RwfB2?c^F|f`d8$zley#6pDyAf-*oYmdokw{EglZSwGr{I6q{O%2{|8 zBI2;Lq}UgVsbQBo}$Cbs%I2fyR2tjsc zlI1l{C$*ln5gCWV=aF&dFfF~ndc1RAnV^NpIMgB{9E}sY!sr=& znHBXFR&(WN!rNLCO!`{8_7>M^!jIZ!Q0s-*IM}$3$HuwT*#{k*86Ib^zR4q%Zd{FN zpb|Z^0=>n4r^W#CW$~LvpLGpbV^ZYpx*Q5PIZJjyheoKJ_jz-bTH@xD+@WLiNjxUsc34hxy6g@7DMJRSVtjqKp+t^haLz6j(CEs_0c&MGUqDA z;f{1~ENrl8Tw3)sEj~b#XCINI4gyEhErQk@1C zI0v%WLf{;hzl1Hm=8p)Tqms(2Y@ykrYo_S^lnXOT$u_+Wp!4S>)>T(uWh@mXZoG>D zbo5Hqgn?n8$iiTxD{BPMq40SC9V`ht+9nrpfxYuUt-gf-I@BTp=%g4+d}&KhS$^OA zY1t7#M+<=-tokBfjsQC7teE5|gbt3inr|xnVtYTLK-xjI7eeS@XF482=Tf>)IcG-E z*=z1v1uIRSn4T1_;a>@jJPlTeFROnTD6FFqX`K`$Znqjt)P>0~o>Y8BM4k6}bCp`Y zhez$ERxB({M-g?jW>`(ESr*m*o>6tq^T!)FAU8C%)aK!3wk+GYw4*jd|AqfIWk+Nk zY%hxcl=MJYQRp<4Yx;Qfo{Fq)Z^T9LhTU{O&A?@86kyB|cx_na>YD zU!L{i`?qIL#b-YI+QR2cv!~}wu#DU>Rn}sJ+1?oJ>x(edtI?L8u!JyE~5vPHmgc5w;FM(8{ zig<;$Sf$gPr90iC91b=M55Q`btuc*-#YMHBeCQL+FV=0{qdYAk7XfoAdWRn)yVGrG zLZFg$l(8{K0o??T0LQchOpOFGn8}J)dcPx=rnjD;)|iKU4Gj-DP@?;ksgN`MA$cJf z2^d>@!K{Qtr9DO;%uiNZ>zLKp?fgeFpfwGrx6O#ounQ=Nf4j&_AjT(hiJTIym|M-G z+MGsQQv_$8XduK3(VX4;yXz>Tm&0DK1iJx*Cq zY`}TH7ZV^_LTd!x+TGBOY!9wMeCd|LQQ9Xf%%eHcl}JNc#FX~Y>{4&Kbx)OgkZ@T>C77M+P?Ghh2;j58h4>V6B&1K7 znt4y_qY;NTsEumy)(Sh~?=hBQpxs63zdjF)<SU;#&$JU$^<3|u-sve$fwA6TVia;OdQKK~g2lyuS=t)BbPmKDL zq1;#_w_UD)knnMSsA?d{N}1cknnD0859Pejnc1tUy#B5*~>js&fpHuWm) zuNu-h0xAa4e_>0sD5QHbJBNyDw1Rx8noi+JAzQ9~e2U7@9u4Yuipxm+aQ<27uO5h$ zrr!Pd?)WK23Nuv$-lTDp6@pBl+_^Od;f}D;eWoA|{o38=ww5uiW57LR_Q(gRNk@6! z&Kiii63YCHgU;=t;G~i$f;pPU_z9@!vtpxp2VtiDiuZXB1@1o-sy8>MmgfJWod?c6v{TdnWXxR{DpzLP}CQTjHlfD|RWdjLPQ#vdv+glvsC4Y-G9iid9kTadE{h8&N^ zc~=h!pn~HqWQxcJIrCPX=-Z*x=n>?hDiQKL`)aPl(dQK~Es&>bz(D=?$(0;Ik<*?h zZJB_&pkm6*DD6@Xed6h)HC^}MP!M|&c%7m=xQVh)x8T&%;;2Ele5jZta9WgaV-?jL zoCBRHtfQP#@0Ij-94r^l;;?ufu6z<5BoN)DsxkAa;D zj+>wK>cP@25q=H{M$ZEQyaf#$E5Wo%H`y^1IKwUy2Yw#<0RZelg}om zE~8GF8cgU%GFg)k(c$kCVutq>Q&rH^Io_lheL=$6fSP$wj;wyvovqmKYuc5kcZfvb zSv``l(+@c#_V+|JQ4#9KnLOVZF7}c6x%7a;emA5FOoe*us8ukfk^G3#5tWNa3=g}T z{mm`rq@7FhEs9{i&Q_Tdg^k=Vio||3md?T*cJI~ID?HAXz&NH*a>y6IWXRQ2K+YV65jx$=389+c8 zNYPOUAqf;P92NnN#BrF|gmC#gBz%I{nZW84MA1T1Qe(n705_psI4z`+nR5-atboN? zxB^omu##g|@s+VJr};W)h(I-hG1R2Rp1idU+Ddv#ZW0}5pA+M4$abJg5M{!-D`&b$ z|8j$zTgxJ)-zKAgMf`1S8o{Z8<{DO$t0We8sgKDK>f}F#Z4`jj6}xU?oR#kw>gD3u z3tXgZ0F?#}oi*pU@e+ySyf)+~Imtx+9E3Wl)eOZ3)_cgmtN^Tl_aH9IL!KU}OK17p zZ-anR4!vjfzUYiQ>E;-_DdqG2Ub^|o=eJ7QB>DsiPLm;DsENTiAEviI|NN8OV|sN+ z0frBgTc3QsTZ(kqet0CT>?Vn)bP1LDC5Qv*n`{3`k=14Y!WHbbU4yg;0nx{t z_1_`k6D<$1*Z{0>b_pUNxkr`Ae{;$>NmoL+oV+U#VhtQ!32dXTI3TEwrCzn3X@AU` zUsE`MX^yR*>^$57gJhizfeA|AV6+`%eX+mq;FQmOP)OWd}%-v(=d7NcEB%;-LUBnqtSfldwc&<$bs~KFF{&eTda_hlo zcbiNw$4=v*0I-8BfvgX+a=fvoWV#2+j(ML~3e?H70`ba@hW3E7qGWtGK)wfAKS{28 zwA;|W`XUd=t0E!Pz!u-LpVA=T|1QIGqj?GF-R}(h8C}4;R2DQo=@$_!iXN%B)o4w% zEA2&aH^c=c@qn8$h$ZSTmD6uYk6wndILKNM%Wu?l=IEcfg$rC8JyDMZ;mqp2|K$rmB7swE7GzRwdG~5>FxbYIn zBMy^Vlm<^iU7U>s<{Mk0#weURkc|E2KEV=EhzE?)f&HQ?s{xf#bd(;ov@i0Td9{6W zqeKfd-mkXrf4VaeHwt~gr_*AKHc*u=yOxL5?mzzH{YDMWu<}2hemU5@|LL7UA@A>gKajYiYy|5RJXRPrc!nFd_U}8;Jti}5)O*+Pw$V!6k=*G*Wl%fWIEpv&N&6a3w%dxWs zv=texV&@_byg@N>ZRv1)j!hD?FQ?Rg7w(qe0;(!pl0HBj&2Xd`uJO811tg%hx~LeS zg|`TX$*_VgiH!gxP|%XtXk@Lhqq+}pemP^Ek8l}8rNDwvv=$<;UTXHdA3G1U?4yzPis&T_Aa)ltW~j0iv(3AkK<}`VyzXd)hwad zn7YN!Sc(B|8w4E-!x@X4OXhxWHQ&IX$Jr4EoXwX>Nzoh;W0PYgrj9@$^3JGaAW-r{ zP!`2}5hrnluq;77*b|ndoPZlNMO2FUO|&YiiC8c10UAIB18li0-XZ}`WVk?n0*Qg5 z-eHO&UJ7S`o;6e3B+k}9pbqQ6Lv6U4Q;pJz4iSJZ)U~$+P@3Xgw!LUsRN)m61+ie7 zn`0^z?Pk%|2$*%PQ@{g?8{KIx&=^h1S`~{N=M+&7@USc37?@9&YV8Fbdz8}%3|P`0m$b(}N_&ih z&E8+lzSDVWc7=n=wa(MCLFdWYu=B)hx61~Alba>Q&qGWP^_CRBCB=`Ku%!4cDSp`7 zmK47w#Sd!>XHA&1mK47w#cxUR`~NY;?@-_Gf8OU{_GIV(OIJ2~0{5RM(KP%j6h=Rv zg2w14)T_tQ67g3!q9wv3YiWr?{=k1CC~j|^MN4EGu|P|NBo?&9`V3kk%(TbQ5+RN~ zhL)&;dC(GdyTCyR7mwqt#Dm+Yoj^!5WOGJk#q)SJb0o&4qs$3XqHZCFghU#AL`Xc( zYf$7K5fUNf%?XJ(Kp!V0e(f=YL>O{s5E7x>NzOY(NQ5r$3`?lELU(w!h9(p8Tm zBX-YVBHACG`!h}|!W=RYA?LwFgb8}YM3jqk1-MPzB)h`K|FLa{4ZP*4@sPj>2ZmNgZ5qq-?bd`pd}ts$@1+#Mmz)+9uW^mp4Lh{90C=w z(*^PHtuu*-60jDChY-0u`K_Q{ogyB>E8|%1EaIVuzGo8;WwtF64>_kcK8=?ipM|`-Y0enffuV z;UKt%lzhZBB;gU)(35`pz7N6l7}xOmv$%!?!hXav4DCZXhOg3ae5}%y*H3W^VG}HH z3oYk}Tc~v9Np7K3$VF^Q3s-YTrZzoAfO_i0w4G{BZs_&R^d zD%?3_74j>P9{Ctn;ee;SOgMH}g(Q8%Duis&vI<}OW2{1sN{?8DBhPq)RXF7MmNVI4 z6>{8p2CEP(zsM?trWLHhnhZ~{3NMm2;iQ?v<#Lf#$g%8WScTHo7FmTFXmQZNljb<7 z@Bk9p5vP!9dn&AEpY*UJPNDuNCpd*VR~+&Q=>uN#XqPkb{&_AAd>`=%A;Hc0gi02A z7Ch$@e(f=QLd?l$@Cm8VnS4UHe)19Ld_oSlAIBvm;YltbRDs8F3A<+y3GEwB%i|>e z7?E%gL_$kuq=HE3vHGj?R=^Jk9v~oILH`3A`U7rc#=41^%i-95)`-?z&UTw(KH` zf)^=+a{OA#;OqS54Bt7V4Dut8`zRCO49ehur@Tx^b|`}+eMA|AqR~XR@`L( z8DIt;HXh0jRbAF;1GU3&C~nX!6xraz5OOyl*!}cg@+%O9o)4r-LA;@7_;pg^6$6c9 zlRLP9RNKvnw>a;Obokro$DyEM0ZI#JilG2(6Zj&Q#e1e3;(pbxuJWPVgNOAkU03iZ zuv!nQSah70S1EUjymE#M6QEzCu0$njNcs~qQX)SdGMo;_5McD zpd#VZaNg36d2INuV$3Y$5yM)D30MJ8=xTxI?rep^RfWWVcRD6I#`K1fxKY!d)w`Yh zlygzy;xd2?Y|v!@8E4H6{H^eRXLiDl-Cx0bKY5oKD;Ko|^@vfpXJIDl@gkVk;a|FK zhOZk>aGdJj#UDuo?H&MjD-P#)IUT^C)fuIL)GAjte7E4S;;VxwWfsqzib*H^Q54r* zi9$!r-;xcE0o<#?Nlo&6#R5=Dn66e-PMrl^pCmqRtfFeU+UX3x#jhvn?B#VQG4T7e zRIEUED}*BbT`4dBkc4cgLYTn4gswOIW+dL?X9I=U--=E-L96W1HsvJw@!^op5+9Bb zPzLq0CV$rRO4P?H985=`ogS)V?&F9HNGw>KW$<@Lvr2_%sEoV!bO?v8S&{H*d@AzP zJhprn@RGC8s~{cFRemO4Wmy6ndVnXXoWQZ^22{xD-9q>aUlDk5pK|o(mW!CJ3T<$& zo#bPIjy%Y4vPHP3)T>vRI`Dzy&_=fmyen4o9R4Q`Ftg;%bh@L^kgyZyiLR2+!s2OJ zwWvDq6F$oN;`B1XDR8xib48D?=+m;2PnciNq zhNRfs#(jlUI{V4^g@0H=lfi>nGukvgR3|I z3Fq5p6<`^mW56y)p<@tWYD346)jV{JU+up8D$%U?u|QCC#ijeVz%atc@b^Oa7(lMpxT6p<2pJLBt2`8Ol1P-cfj{%f5HfFoereEukDZ8QKTbOs#yN;zbQ9w&+ zPGM#nMTTn8mDA$>JKALl57VuX5#gqFw;$i-A-be(6qz-qf_1yB2-PZTqDn9jM zC@9kA0xyNd9qKoaB4bC=r2JPEPniK)KPyxSh$3@=Zw${CX59pd0hOeXs-t7=R2y*w z$?5eDb0d15XByOn*Q!Hl#b1mh^VH)a$vhpAWd4MelqI^2B%}Q?BFW@uMv~E5#VU?& z`DAtFP|;`&^Z=X%l0VV;gLqco><;*1I@Daq5}kJXV}Nan#!YJ&1lww1MA{Jtn_AF- zp0#>4Eaz=3nGtI{pn;ti$ynCgSTcw#wXtMC;m2ah0BnE>NK}Ir8X#hb>ny~RA^l=J z8BCNBPew-`{swYdm16m0^2Bq zgBv_d?lOX=o&AR&Ku|lq6V8w+^U>hj|#Y zl@Vy;%53qU@%#!S>(^O()t*>PoBC3J@P zNn$DlEaDfvu0yUyzEJrAe}bk2jkx4A1N?0621e6wrHw8#q9ls!<3tm#+SNg)}uixtc7^iRdVuTseFGiRFyhVf=Tz{fQF9G%nO^uskW8j^B%xLF? z+!CTQZB4|RM?iNI5T1AZX?Y!G%_O1yrUVH@G;#I8Y(w!gvBwI zAoOXk*kXvUadVBElyDen<~6>utr(8gVM=49=s;DeP?(Ax5cnM%JC0ff{?H_tZhXct zDInx^6lvyqwAV;87r0A`8iR0Axp&eiTl1Lx*4dF}m`?V1nD5zuvGtg#;lx(tjA%1Y zJuceJ(@2wkwR0%Dna!RF`OTA`&(EHX`zJrYIr~Q3{~vw!aW}G@$9+CG>n}V%`Tg*{ z!~2Jyhxh-Ve*TZ@d9r;k;3{RkPxi-Qe;t0lh+CEE4nI%In;y!Z9)1?^AWVNKk9sJZ z`rWyVYB_r%o*&Aq{=boRJwNj>8YmP*3Lw)647EN#0Qac72HpZ>!&V92LOh)rj~4kl zxJ+S7C31>Oz$wuoP|CbUyeEz)&sNl@D~bl3vMzgjl`kl-2h%}$R!{JGZ-Q*OHc62^ zIVJ6$fhGDnI!L`_cP#@(lJ6w_{1$rgbqNNg2%f1O_RZeI4>87~hA9gD05K{yCXJ?0 z_Zbo~I-h)^Q8|#Gs;|HJmAV3ef7H~daRJ!PD$+J!;>vJISaI1I0cHz<@ipGmip*21 zsYN-@vAwn$PE;-ZOH$bdph^I^Vpg-HCa<*jMb0b9t*nP)D)n9O)go!(DO&xQR(jJB z5=na~IUzNw?5gVil*F=)372$6$s9y{N-9o2q4N*4G~4Mnjt&XD@X|-LA=4S8YG9*fdnwpbzq@7sLwcmRzTy z#cN~-lFzZ1cd0Q(c520-*q_r|*v6-~D)7IXHuYl-pI{UvAEeC0=~}76uYyeOWgIHw z3%TE8MjfE%9~CBUWtxSa1Ooobs$%AzL9Kvhz$c068zyZ;PvWUt8E1f-SSvQ)4(@S0 zLH~I|;C8EWUeH2PSlFZ@%qs2YJt9kS@VN~Ig!Jw;D>8*T1I81INzhP62f0t?Ip9bU zPdN%>;9Y*1zj5W)$_U~rz)*n{1e*UVaEB6D&?W%Pnu%dtoVsB{o zqyl`(Y6XD8L`eJ^tWw@yB{f1lL8DO8H3iUNjhx_@tVz-B0hwuDS6RN;xZc?-I@#S- zRb4%wC+|t}*$ou%2MuVIVtCHkyv^V=;33+SVC0ItiVxT%0k%g9hq`r<^^rb!&`O|+ ze`#7uWhTG;wfY2XnA9bqZeVisfduxz*csavjQd(N+25?7`Wi4+I0^pEz7bNH!4#R- zAC*5zMOhO-0i@qr;lKh-DFoSa(YWZuTg;>yALUc)K&b zt8nS)0{K(tK+5!9Eox@?a+wp#%F~{O{1EZ zgo%|OlMj52?3>nu4%FlgXG5Ddk&c{;jB=kFC0$_L0VjsyqJ1vqt{C|)gLY7loKFV? zBMLGHb^LW_jzu7q;fopE@R^+s4&$cUsofVO zPY0WMiaNtyAKlF?urT#;FG)m(P)=RNK)7cmdOfvUjp4wV1tPBBio*$UHjO7(4;UJW zCZyfzDs`cFXJ*$mNXLTXxl_HJPVcB;I=(>u)LH!9qxTxIIKx*<#h|w8S;cIi`&7e1 zD~W&g7#J0q%GB|i5K)IF)#TCCZac`ub=uu~jSQ(z!%n$N<*^e|F$67@nwah& zWij21-8T((dpo8(YBxl*!-svt8Vu^Mhi0pXFHrM%!u!-YlvSCNc7iifv5RN{kvICe zQza)bgHo&^1I9q3jA~DX+Q}{ zfAsnBcMtQn+{3>QKTp=>u#ShCuIjw1_v)qot6H#EI^RSW_J!HSPCk3JQ_T2RmW{fHU{l15CF^;2D59C>?!%17!k^x*={42m>riq89M*8_=41n*`uIemEew za|qCnG57Ni51Gg1u!Q5#~RHI@?U?O zFR%5!RH~FC2a#UuRl13(0mB<2Z39SA@IF@oVWL}f`TqPoD#SWXWIKRa+HJ&n0G>!3 z^cy@IyNT@Me1h{4E!JRfRMz=hWC@%GsIv921K0&^?|8-#pi@8#-y}{8vIbz8GZ`=Q>plP*x-=X!hf#}Gz3YNZymD71UU=e#Y}bt~v#n6;zJwg34oF!yu8 zBSJNhG*_va6Ni?gwQ5{1t^XAH*US$wxJs~wdNBS23c%CxEe;6MF^5C|GhT2YD8z1y zZP@#q<&Yt}6O?!TM?1V??~}`xyCR`B^)n2cj3l2&r~I5xUbW6nw%|OGE#1m?kFwZe zbcycZVGbM{M&6b&^X!LN_9hRh0B0F2`XYs`1_-m-h|z$8D?q=lHg6DESF!m5pd)UV zG$1sWE>mG44uOR9`y>QpiStuJPn`pEYzLrC0@`CxErd0k<71#zwp7S%Rw1GasMFqT zAW9^*_C7>?OI5CrPzBS0_N)ky5D|Hg3;Gfa2=FxPYVk+-@(gEO9 zdlbAqSs4bKhcw191Hc+;V1*v(u5|hS{5&kgx`3bd)OIB7Yj(fUXYsdC7ep5L&<&NH zu6dg6OE>5lp+N@T7tn#_*-5y|Vv92#xrZ`r1R!@h(w1&3Rz{$`tiL66K1W_Olfhuk zpqo`*ORn%h2up%hwjfE20lBbIimk}EJA3_3RSHw5NI&w1Sx`h{N@E2y8Fl*$>d4K^ z=%54oH1-(1&X-vyU$v_*@ccP=9yjO{iBmSzp%=T#;unsBoMXf+#mu?(KilHv=Q`a` z$)y4PtJKpONEs^w(qD|D080I(wtoqL zo>D2k0+)!iMIVA`nJo>5;s{oy2$ld^WS$TY>l_)}1cG9)74%7DsTfJswZL~FXAjhg zm{gd01z|!vOyslty>#y$=s1@wgDR{TjDE8sKsYLdJpkh~(ewyAHXFUoeNknZehEqi zhm>MP!H?E+@p%ZzL+$970{W771MZlDl)&eZnB5{XG6E9`d;>7?eg#TGcUFu@H3PRH zh{`{e&_iIX%xd}rY&gjW(oeKn2jK2SoPZu27sBRn|M~fm$~uS+=-ss&d*?w$ycDMa z{4Fq5wNxyO z$gYllbO}QNq@dei;J<({6(=nufS)>`=nD#GF%ya2pw(0Sb`lmhKbP|a)rSS_MZGi)3G3L&- z|LG=gKbLl;`S)l+d6kMf0VyOOkp5yE2vF>OP5?YniOm{k(nG z>N^elq>nQ%=fA%V4wbq=kfP`YtRDD1&UMVelHX)H*qxWt-QF(ZVeb);V2DNfU98hy ziiv5F*4ywM0(&Ve=Jggqz1C(=hsc;hV{Z`5>l!BtUW$&r&MAV|RlunNWL~RS{=Kfu zz8ROIWM1{`g}5%wvX0lq*|!KJK4&<9t#ZxQn)Digg`ti@JmY-tU>l+RA$GtSJ6Uae z2e^|kMhUNBKg;SRK>U&mF8E$@!Ixa{B^MmLx8#B^x!__t?<-(2T<5igzltIGu!EFZ@Ue02BY zWDm;F_G!ZjTINdc^gD>-D~W zcfL^GT_79T`NF-si-pqe1;qQ>h2ri-Jk1*)?C3O}FwcX;FkzlgnCBDbIsQLFM|tGM z3E0qii?eXTJjbt&uUEJEgn5qBe-XesVV+Nz=izI{Pg3%vSP*t32T*kzCVoD*7rx?Gy7h}d+Yn7cyE1ANx9nktGaQy+J@*4?3czwq6N%|z0_82ev5=`et@O+0^&f6I}*oQgJ5VD6j&hUNv9A^e=pW{qa zVe)LB+YBdtl-vB+SZ*`*?sJ>h_qfeO0uJ+);Z5}alpe-Ck*UA?YC zE)7nzYYci&L?f~wM>);aVNNr%7M$j@eNOYqF`VY?Ag37?Y;c+-?c$*v zQ`tzr0aJOas+u_Eh+NEx%I!W;`OyPJWfp7@l_4k}A}T{(&aQD!t_`BHb?p(ABlL8b zsEi==C{dZ|guZQ1YED!>Ifkg5u~$7pREFFdL}jSBhl$GWS;O=kCMp{%cZjI`u|p(f zH{bV2%J4zc3&SMkJWydj!@# zL7CvhF$85;u|ZIlCwq{foYT68$;r=3PKIt9BwjJ@NA!k4Db994f)7e8ZuRn(vV-Gl>-{GBYJ>>OhT6t z3bGXE0}N#JNyhSzdr8obp>quA$I#BvWmJzu`mu~_>Bq1zjNA+QF(bH7KQ`EIKtJBf zWU5ncz&>vG*~gC_U>~zygMBO?t96;4bt1g7gNM3!Ac-Q!(l-UtROwuxpiaxl2tMEbD+ zA`A|!Y^<2nYLqEh{tPSdFwf`C_XHN=fA6sOBx12uamX*7-Dqhb`L z2^j|lN1UY9s%lcsI4v-HgZJvma&PsP@qd8IS}hvE>Qrf&@^72%g}`V&mRKV}=w@jW zy5}P1e#^ARXf#`ep5XUubfH?_AcnGWtlWD$63cv`ywh%||6O2dPBH|NpkgY5<;tJ| z0?E{?HswQ+@Tr~jN}3R^2Rli{jHx9|^&RB*)UUvF8@&`t(oL{QlTg2sss&Sz7rv+J zTcKM;Mz|u%Fnp{QTDzz^G&6N{fm=kIp;%4C+w?~ZJ3K}Mn3`Tg@Dr1>r9$(p9nF-4DB@I(&65uSShH+ zP~l7|Ra-71=9qh+wek`fYUD9g-i6>p z?svJ4I_s??Xj!D? zRki#MrOBf)PR)L*Jb<))&bquhd_IA58CaT>+lHDB8{pIRSZUbD?~V zm-9v8>L-~ASHWU_%=xQzIl)05=V@uJ^~3V!VSM$%sSH*D!{vGNgh@cZ$eXti>RX!t zjj3!IU{NBoM4~Yt&6}q`6O&#C%^k{{*9fybE^i+70R-+`120qy%K!)nv0LD&Ysi-! z%$tXoREaMT5|Rd3`^wQA2wuVB2e_b32HA^4B3NHAK{*5QtEah02!wpqo!0@A%lz~? zj3m||d!8^PWTpc?AEy$ajv#JIy|5a0>u1jse~E&Sh#)}*IV*P3f^quFSp%BrN=Oj^ zRJ`MX?0Gb5Z1y|^7KXCtc^$92WzeJW!3=tg{oOL?g+d#HQUUm7UOnKD&aG(7Mk>LkVKDygCu&mStE&_X$`M5j{uV^g?6RN5O6Jk8sxlF$wD%N z5vg-%Kan1z2u5b{SjjQIoQ!U8-~@LAQx2ILy}V`=z)q}D@E zfY7E;Qy`LQ8`Xre?!xh@^{5W<+&edLg=NVqPAilEOcHVfL9&CX^>DZ<*@dSw3*5K|Cm+no)Csrl>AHx#Z03HJ28qDI|M zu!mlODhT@t_PAR=!5%?UM*S-xQOQyi5O%6;N>UrD*SG@disKgUW$uXc6mRA|2%=^6 zTD?W1^fxL|#=-Z}t7(1h>T)e3C~GLep4aiZTarDa!tlou(e-|7<@Vb*TA5NDcT2Ko zLKq!_H7=+!H%n>=5XAk#tmQ2&XL7qJd4)W~bv(>*@GxOoK_7OtEYDuG3 z#Z!JL&mJR!*2hq;J$$4rnSQ>#V>JIH6{`U)>Vq`XWygl zJp106(1regqdWZn&$6c?$jv~vyS+Awzl4_}aG_2*op=I0r5}VRHNh4*2-#OuVU>sg z>Ov#fT|>*EU?G5OwQQ~qv$fR)xQe4$1V3Jgfd#~|F23V6ESA0it^!%3Dv=jtk9jNW z6l!>rNp*n-`HjaY^?86?FbhC#iLzD=;gYv}4!0qtbQ!kpFK^bBP{VgfGM2Ir2~9l6 z;$7Q~ARA(nlm}AO1?rWPcr3Ac2wJ4#bM zK)2xqOOh_dc_sAkMj zzaz_g2>jygOlOhGNWw9Hp7ACNcV@yBB++n|!%C1coi(V6hoRkMqws5-6K>K}y|pwK zWWiQqk5LwX%?#om+^vGZG4=f|4JyAK&gzof+L&Vw@N&auzu*^WE{fKs zt*40Z@Jc3_e~PoU&*h^Z1|&9er??$xed+6koZ9uRa`z z0G+fht&ah7ls)F1r-g?!4)x?bPYuo2m2Df-I>%TCR-XabQ9~oC5O?l3J0oogscg&< z$!OtBM@EvfN4dgPV=q|?YoutYmRPZj!S?i2VHrGK;XFg8CmDnfQqBP>$y*ROE+mWs z_rvM%NMUo;O#vO|;XSs_kX)wI+`{!pz8&?LfhFe5aCcZ-DHJk<-ZU8vKm(RN#JiA= zt=CN4z&>ms1g01p&x{T~Cds(mwRvx1yw=OLZoZr_^1?EvV2}T15N6xuv zZjP9685Of_)XdltMobHRGQ^O2zWxzq6v?K znve-=UhUGL=r>RWwJVH8>|zUnxLxN)^EjPR2w9cl3{9#PmyL_qcgi1 zPXhM987v(OU_IAw7WXGTNUzyK7`b9+>gSW)8)OYBn*jwmvR*~qUH)L_M}%-ZfA|FT zYn&Iu(GBJ3d&Ge4M8e5BrDh> z$>s|7HBsAv^pT+h>@$g(fL*e*qC)x{`?v9-XLdL4d8Q!l+9>=HCQgFkM&S>zs(rH? zb{aDrLaMJ&0t=kBuz~FL&sMKv{4j`azt-6|ZS%#HTzCO)mZv0vd;p6h| z#=X!Tr#YpJYJrk+5O^lUuA8du{p5Dde0-wFH4#J~q%;5!qReo$UgD~0zKfXNr1D^G z0vGI|vbqJ^b%Nu3PR=uok-d2aaM3u&5!``RMScez$S7~qEU`=gLvT+6Y-0gMa|83( z!jI<2<1|HqiEq>6oZ27&JW}qxPV(oige(ob_tr_q(CY%c*utBxkt^_9c!sx=)#(yR z8@>T_ow~I(O}V5bt_A$ny1*zBxKfv(@pyTYe)B(4j+9njVKl|oHv1&V?2!<0lloCQ z%RRwE1!h?o?h-N&gdHdWZV}?`T~BAqm-*X%I(FtRYbG&3s+CF!OTZ&x-&c4CN7S`$ z91GKkwTpe;S9r(3RoLHE1ax}p(-fwl+r(C4vNA+jbGQ>87R;__fPGPPA{C-BWl`iuK9WzPC`3tYF0S85x;1%96 z^-T>B$HIDHkixz53V+nGK{Zq^72Is4pQbPc$#@l&;CBHv_7O?=D)eetJ4~~z8i~}@ zti-|;K`!R*-Pw3kf0&4>bR5%p<4t8JD_BM4D6y=?j8-T`gc&L7?jRwT7B<{v-W#rE z4LQ&-mvyXPb_(l<3lJxYfJxg7`Y1h}+sXPOeRz>$^n+&aEZ^XMz4{G64~q9YxBrCp znL&m7o=nIQk!)P02G!~*O*%gscjMko8w3#P3&aEov8FCFP!$+6tY9K~Y zBh`@yK$mD%QxnOYz|LxbFwlZppEX9nf&wFKu~PrJ`^uIh1&dZ~Qa;VKjK^zBx`w!@ z-@RSa-u-UZ49B2Epl@#l(f;05IIWN;uhsyzw>J=a>U`sBhPeN?fR8J0{wcZitbkDi zGoVY0qX`Vq?a(kA33j_fG6|e)!H`_DT?>pmi%8Md`bm(^Bcb^k61!(ns}-xVG=$AA zNZJ5fj>`m{ey5AnW!HO4HC%&yQ8ZL7BwzL2BZJp5B4T{0k+k=Ea`&;q-b>x2z1Jsp zA1@5Q)KeOMsjYPImF?D0;vfG~hw0z^wQdG)5l~ek?QNCX!IY zmu6dtRpAtWs!nl>$0;MRHDDIq*|@#21TC?~E@}-F1U%iz($b^2-6k~12@MixRGH8q zCp1XG|AYoPp+VxLItpf*&>)i%DF-&e-?gO9(?Ew5$8{Zq0e|5D+_-h4gOWdP*aRP) zKD(WA6Tjz9b7c+Wsq^k_^XTevTrC1gd64i0oGgY+Dv1xk+Ob;=k|rG(5BYp>)4R#D zkQv;}q*eI_ezR1a(;B{ChzI&QDc9xZJYXf}5%Wx?s?SVNj)zAN=oW6Ydic;lyWWPo z@<3At8ca6%KD&;*IIozMjFzD;O? z6PnUaMd6|{vI2_ZbP$+T#|0b9&`3G zFTA0t(Gfv_yv9klnDK?ypWY)tKDkeTl$$33lC9=G0aBu3PJooQI81u zzO+w(WRE*1K*|I3IyS=LFiL<#;)Czj%BFbGA7N)^pW-|BW*dBu{s=vDpZ?f@c{h&x zxXlLoc;@3~a(kHfNP`+}+IlqJi|(2eO5W!?I(~b6M;bTaIueI5YlJsJ$#5NC-~;12 z%5&t>;5l-6<4j&4c#eiMJC=Q(BlOCBo+HO2LhM|qBC`aDM_BY2Lil;Aniff9q1^BkMyX8~j7Uje27w z^Y!_S5+H~9jW}w8-w4;yAk^wh`}{`lEx!?3prHoAZ-f^IztI%)`|QT2;PT=7${=P= zeJbw+9RCq^Bg#F)?8Zi+He@scMsAUWlpx~!BNJO{Ai_9H0#11osn3A z0iBWd4ycR-I!qG>&JT5u%6ObF4CW|}kvSRA7}@#{8Y5jDp)oRIhiHsYD)(rNg!A+x zC&rN&3C-CfF+MR&Vk9N|SP~;N$O9zCW1}R-mmx5+NP@sfmxc+9XZi$2=FXTR7u*Pe z(eDx%AvK1EPY@U%EIh+DSx#VlMgk)&wGjfN{`@}`Lg>L)xiHq#!jSv?hv;}by5@Qe-U)m=w zdhZ}E!uONsAH+onZx9z%GTf&v;iiie_Ed<0-5CpGYjt4`C5n1T&E?4KowZ^qGmwT`&{r+z2z#?=lnJQ64Z89}Hy&Z`r0G3~ z&D2TPpsu9+Udkn<^=cV1AMIT#H$s7iYQQ$T1XlJs2%~Hgsf!q(j6l&4cY+#7kSLvE z+DsqO(vu*qJe#gG|k6_et4 z_ST6y(HO z<6r9BK{;(gBXlWG#s<5snv87_NX2Vwkwlt=V3_u zuXoY4khmW{Xh0@6*l89d^C#-taj#tVqo{@kfsc~RIgD{8j5613 zF)TD8IAW6eW_6PKhE+02eKSdYGf90jNqzHIJ@t*Mh!}2-{Vfo3Z>@v12eyJ?39L|m z1n5u9+E|9u$Ht1d`H zm;y-f%W(iuxP()#`tr_;P|8cz!W(2UW~ocb!0PVQ?C(Ax2Yu(z6eD5u2a|0bk8eZnS6zJF15`rwn4%dmhEW57c^lS z=y^uZj9!X_FZdV5%ju~x315(&o&vBIYoJ~{YemIBBH;_DHiCBrtpupnEBe_^a1g)Y zaKe|PK-snXMZ~25z6hdZYQ-U>fsRTDIveq9G_ATGM4T!RqP_=`ESs;P)}o3_2#+8% zIF$4SXgrei1&5=OB1vt0(ia?vA*I{~Q8xt74OKf3@=)b36!{*ccYGd7{6c*0!Nf1L zZ%pDB9t|S#O9SbfgNa|1%AR6GlxQ&odMjw;*28DWbn47N6Smr|zeQA!2<$H4HxwnrN7|VdQX!DCvXrpl=?xnW2qGMXC(ihtyW!A%t2MBRwc_nS4HgeUMD;q(l~ zTByu4D@Uo|>iq`BR`i#__9wbQH{>J67(^U`ao8NG5O+Js52I)CVzPM)*_Rag7ygiO z@?nCt)fV@0zfMGGEt3T9}f{4(w-d39`o_B*<-lHX!e-T&}I&1dL28ug}Kqe3^E@%kU=IE z(SZ~)F<}hTt{OAEw%%T0%*d*vFl1zrA@F;UMFw9p>uxWLjIq`@bW|%tD8!E5tWqPTfMuzD4dR9S`6G;htzPDJ~kw%8~ekhF$*Vez1U#p+k zWjIJ9$k%R*{oBB>@uZ-xM8;q?n_09xpNlCQH=B>M60ki^$xn3 zt0NU8hT9K6Uq_c}0>-Fs6uv745>R$PfGFMURGYePMJOPTULEDQ-DMAOn(f^LSTB40>%_qX_o-Y z`ptCtTCj;2pN{81hTsq6zPFNFrQgD{2^O+cpb^{1PBEh>LR#S{;?(uEW``?jMwDY@ z#7^!CI}Rc73=FQWuh0nbPUc>-s&(}ZJ03zb4C}fIcDvPSF5zea_nqQFeB%fTGI{^d z6{I<*bQFdgmMR6TfZAT84m!s9UDQu099!p|q%PqmEnO4l0zrYi%Ij=%)e>W$t?EsV zyg$NqhFnd>2pq>#i+7t*jzhX6Gb&>?(Inv#9imKVbnn9!y$&MAb?QjphY~kwd(=61 zP@@-4w!JvZJD(d)0K-@!#}7w8%A9SWT<&MYaCqnc=!MV)7e+jLRK(fB_i&@zY$qVR z#@^u?wKs94@^Aq<BAz5d1&YSY;4It3p<4JRpB$FkRwqGXCd552Ph z&JYsB;i@Pdxj%NNxVdtNV6gW*` zsYFf=we`#bqQFYq6JQJWubErqo10TpGknFsC+DbJD4Eu&WPdUHkDsn-nnDI^6mDlMWD1R}%^cXKt5bouz>~{4# zNGpStEGpOz*lGLOInR|FODmialGDFNx7M+=@^^6scu|z)F0#W_!u5@u&Cb+n7bQJa z@hs>jJlML@xM?@s%k_G})v7Yq*%a<;&oHk$9d--&#t|fO@~)$JNc&Fd`F2_a!m&}$ zx~RL0k7K+j#X!_{h4&VXMHszTWxDf60Pn_NaSx}u>qNDq`Oc>p7@Zy00C$f3I}f=A zrpyoH6NG+v!7xi8V5F!#Jn~F{YKpr1KkOA(P=P0d4(+4J*3=rf{sQB{m`9)R`J+Nb z9n6^hN|BHo>ot&Pm*d2+ZZ9R70D{BC&ThR4CPaL2<5ntgEs?Hn z7m+`bPbAi{>a~RHrl+)I=a^E32s|s$xeU=z`|+behia71&TZm-Ma$z~Bp; z?nOWqQl;FnRtLL?Y^|2?UKPU2RZ1Z~y<0}|8Fac^p=S~lYTQNfo7D_uB!#YH51II5 z>vU6?oggng#tW!}{P^Y=tx8eEL^g$~qpSZBe-* zfQEF~c!#S3)UN~6)0H*CgDJ&U|87}YLIs4>yJ0un-1d4Q+lt!P^_0za&oEs(-MTCV zrHW$c?K=94wC|L#K3bRKX)!mCF5q{9pvnHk=X_tJAq6CiyC`fU25ENL7m8A4y+-@f=ds73yr3lcifUdh`idW0tAVV9p z)ueH%bE)D^eZ4l?RO)kf?FnLv#oBgKsS;|=nspd#BtI{gT?0XNl{^_YW|UPF15|Pq z&`7}f4p!S|B)d#!7~L(7lJ}YK9}l1mKxlpv019ZWE-P#U=Rn}JWnLK&?-o!aArl0C zKTtt?ro6+BtKb&E5!>##X4Sp<0p&oUY=pZ@y+$4tVNWkz1ADhcu+rMN4ltbRx<_)T5dO6lAMjFE4`2psyOpD9Uo*gdJlh zCoj9=J3GmsEPl$|KV9a4?KlFeEV*C(M!zTGF87S>*;+HdzjVLQy9T{Q7C!D}_3MIM zkfGkuRjxBcolK9t^o>(AWTWthoc0ocl)mN80`J16S9#|FL|`dao~8FuPWaZQz9t93 za{s1CT?VI z;BZkSyS*T~#T{9!=NP>|kK{>5T4|NJ6AI7La@%%F$L=&T-0E`jBTy`dz%Rf_1-W;or_^Q?O3h+%(ZvRm-&6R3coxG8kN_36 z&6|RW=BL*M*d~EHrPpz#F90c$*bPAgLe08VG3OZjUZ>lkZf?nwSAeb{|0NyVgBik( zf|p#L839AEkPU_r@uoRSnn1)z&&T~sb)Zi}SVj4+?_`xhps+V3Fs#sm^dbjs(Gf62 zmBe_vSNgpY<&npaCr8Ex933qYVCan&PDL_Ap)lW9g(AQ#7vk2`>a|k9ZY8_L)20uvSXqlIWJ7lBJdDiJI_R@vUzvV<#K2}TAdY12 z(xj7=nYz9pl*HYL4NSDYWhR)(< zK_rY(LA^uUYuq9TymTsK3RJ9&x#66NY=ordlXUI^%Dec;y5kKT!9UBY1%6v&)sjxD47h+{ojCbTzshv!AT4|sT%w-p& z2;}UUH01|k&VWlIm#PU-fCpr2Su#xs2q9z$!`TLA0W@Nyn=K6c&A@2tvQ+U-b+2}~ z6Su60WmJbzPqEa*GgwDvQh>2bj_XjMtRy#WFjC^Tg)A-bP{_$}|BA>d&=cj?gd3!b zK`xP=VCu~vQoGo1(GkE|rcC#WSV|OT&xAaD$Jvy(M7${?7V@O}hq+;u?<=AOp%v#F zT&PG+l>u$8aI!?fj2|GK@G{^>NjEc~eY!Ny2g(NAr_1W2TR;Y@qZ0Q4@>H!{A95eo z1=<7n1OSz=Ye9u>O0A}nnhC->+qKGT;R@uk*U%4D|8bQLieWguQ(10_U}y?xlm`Hj z)4j=iOknG~znh_|$4UC{3+Lfhx_+aQ+D$~h({NJ)rfOE!p$gm3we!$OD^~$vwUB?O zY)OSqaT-2k?$V@_lqr)z1-OT!u~-ZKZrx6JJLVFE-YaLTOz~L`&T`O3o{|iFwfZz) zd8}x6@uC9iohj}sr~{A%%A}EG6T-8Pg11xITYiUW((~?%hYCSnv9=if-6>sr=Qu|M zf;gnJy3bColm@4jT<_BKSWk?PeX27pF29%>&Y7S_(5Uz)S z<`r@9B-`MhD$h2ty1?XmSuuy2>hrgwZ--v`!eU z6GrQV(K=zY@|;i(!FD}p6Gm&fv8L}4uO&>*&Bhz}+6%b9s~Ha338QtwXq_-xCydr) z!f3^HHes~laKoKBVYIrVJz=y?7_CpSQ9EI@P8h8dM(c#pI$^X<7_B&(p}tHQtrJG; zgwfikcT50n9&OPuT94ALyXoxhq88n&uWy@AwDZ?(D8g$Xrwux z^&*$agOt!H4N*QTjM<#e3URg1XN8ft&u5i5I>Ke8+$fio8dFHS!DS`G_FcHFv@W=; z+oJZDNUSi0gTzWe;CK?N@n8o@tQ;F9uzuif1lIe5z`BItgol?CSWiJ63Y#^i^o3Ieiu4plnnVa3>u|vN?Sf zjwRbrlZmU8t+H6Fw+Gos^^z1Lo=p zZq2!1t-=Z&&srsj9oFja({^}_1J>$evR0vF4p^)7G?U@UJr1*0seFXBDpiu^2Wu7P z)jn(W^jOxaVRcjiYgJ8eok zlsDR+SkVWK~F{Iaw7Z;yzgwN^DM6g^@Z$R)ssb zPgb>sBSclojuKVjMFvro2pk!l@FVX=RHdB|RpBC2%7*lTOnQRjd=8GPv{z}Ghd8RH zPv_^Ba~ua8)yL$hLcknwROxA5`Xcu@%u%KC5ss?NNSZICl)<^rQ9V7DqiUcVRlre| z#yO6nO3Uv`QLPS9RE>ulrKs8jnlgr>N?9qYhGqpp^)x*Ug6dolR8Jlvs4{;!L3M>` zm7qF9-PFHFP<@K8K~P0?c055925O(6I>+TB1Xbo~Ku~Q(ZzZT6>&tMi~aB3i&2-H@4v~Ik} zJTLK*mfb=T@NOVYiO{FIks^w2Tg1>Gv5HtI>f$?qrC9m`7>TfS0><-#r8YOi zfC&cxz3~{OK5v8yaEj&-J41a9ExosV4$Mj^(q;DX%nwQmUbrfTV*82`sz917)Kpcqsv7q>sTP z2E7~gLP!yRxpPq@uu#@848!Oc0w|1?=xS3Z$$JZDC zkg(z0N;g#>2R{+0-B>p|J>x>-*$I^?rbUCp#c)7^o$k%Xaqy91u63e*swo&xQCWhu zi>4u*ww@yOTrniPDx5Jeh|kHfh)Nf<*evc1aOyRtFBW;5hP+o z;)CKyLR6F0lr{>##wXD8l-*EU0LVuW83_=n&0wV}k_Wrh*^pEs+&OKW<1#%%RtYG<dGDE%c~cwCd4tmXp`15t)*r}ub9^M{ z%_|Y1pKD2h%R7(ca!3SZm(FreL;f3!d^4{Q{36qFr4=>Owyn~Y}xCrvxH=m|3#(R+a23tFt`v!A+F!v26 zJ96KM0-A-L*KdcW4sc>G_YHP`B=-%ih}<^`2*Hb1_jBKPZ*$-1oB$j|?weDD$v$q# zwjsYpR_lUE2g8BXm$Hp4|*Z z#Y&+8+Ic_wZqxX5!B3YGDL^y8CKe zO^9=h(ldlU*9rJeb+`Dk+WW#6S#2oKW90cDDGq+0NQ%P%}4JYDRT4> z7_DmD@u^6WgPw{-jAhmhQX+^plH?ErTYiya^n+%PB+0?gbo)xnF-6HL_hF+hb3>=07BjA%To;jN`n2sC-3hjB$*n1$>`wqr5_J0BKr*J-qzhqX zm(X%XWDh7J7y<|-HNZhf!;IS}d%XNTYH602N~jE`dZWzp5;B(Lqp2lAsN^z7p@ji1 zW3Ul9(mv0n)l?v!hlikQ>hrw_NxDmfa>H=i&t7GN3>osL<4Nf(z zsl$sJbZFT&x7T!Ww8%AtV%`P%O4H0|XzG|0^rSkFctfuVE-!^%5=UPw*6+$Dr}&Yd z;uh@T5x*Z7M0YlBZ!DqVU2B)iHLwW4ZYN7ik0J=Oz5>v>hIq`oino9ii)~b5scARV zIf*x60Yp#ejX+ZQAv6#3jaU#&va0Bj{qzLb1Gl%Xs_KwIcYmc51%*hv~qu`3lb zlz4%T@gx4U9A?4V0EgWNY!s6+0GyA5Kdx^gk&d6 zGcA*-X4f1!%d|%Qa$AM-@B29aa5!$X5T7{)EMpTp;DVkm(mg>>+cH>%EVIB7; zL2p~^UYAA2rUYs~Sl(J^@Wf4OpSG#kb=6^dLnNr_P9VZ^?5}h{NbYzgdL^(pO3vxp zsG>O7Kp?s{g~i7`a!0j0kb(vpJC90-J3as!MXBATVoxsiSkOQ0RY|tP#v;|Ao}H^` ze>|L3dO7xla8QKVYEotFw>?EsY+csC_?2M6tI-X7U>l0Uev3C{Wv< zptrqdLlvTWlXA|0YB!9ZJ@Ubj+7C(&10Eyn^u#V?waqWZ&w1EbSRZlU^K z;>X4H%7QA*=@M@sKf_|=-l*O1my4skOaTQ1|2}h!2GUK0@7*6p+_ZO#eMx^#I}K%P zSSysIw9_SgKgta|1s_Dz=m{axPA&c{Px&@Ht+MIEGNw$uxWt-PnW5EEW+V$&;^ zsFfN)MZK~+M^)vTGaBXwo3bRewrAXJv!zRJgbB({02dP!^Hxx$DQ4{Ac{^HAtld!r z5qL;jMWZ1G**Xf^P!vWfpz`C%_t+@Sl1_G&ZMl$gvG;*7P!4R>MZA?~-A6;15TMs0 z!6KWhj7Q7C1ef%8E{(z{){1?$8L%@8>-+;ErY{Mf*96AM7I*`$?a z`boWFy{2`7eM&%G8`QPKY&X&Xlnk8-dfRI^l{0EKDd!9@g^r&hEq-TqdYOYmLyK4z z#A%e2sXJ3>bt{@)LP2qp4%L$SDZmw5h4m&q3gAdrZ``_pD&-b3;E94ibZ)2I%pOg4 znk#Dn(I&fqZz~JKxE5E7aT6>ccSjmC6d=(wD%B9kVo7lW92QlD~|$YR+3^tDTPH_*;^?-?o6-fQf6`g@|8DBu)hkC|^wmro@}lWjJyeZW#2DO5*0x$-8R zmbUCRqO*Mp7F0!8RBNjl5nag~^&5z`!~Xwi=nSe(dJYapXiS!qXduWYefxdzb-1PJ;ov71U7NaSTX zK+U;Vi@RT^qJS`bHuH(_Ptk0CAUw}fSd63&<8>cxxP$;(qLZLPG^61VVR%o$hEQ&j($=5q9tTg28bdw_t^Z649p zUoVwA^e#QYN$WbvI|Y#@-YtmPKLNmAZykXbmzGz}AX?(lf`2k_CIy*FUQSIdwzR3r z;R|ifE^QXyQ*|3y8W}}**O(mLl8$x)|z|SnQEE zV<2~3h31*0Rl9W*ITO=h^1xGNsp2vIHl>-jxWZ8AHeDh)k56qbl&|q}z9?M%BzJSn z7xQDzU#-gt^&q>pwALzP8}Tx2lWjyu8K4JKskoqfWp455DXM_srtlROO#vcYLx*v< zvOvLmat>U!46tC|4C_sCtn4;hap({3BvT=dP2F|STm$Gh6*xrgY5+?k%rZHj@M(3i z&kb%4nXQk5KTx=H4ZKh-EDIt~lLk*+BPsOG6cZc;14$?H$iRN4p`79txpR$nT%pDQ z7krMEQ%xj65lm1{*JJfG7YTuC_ZRNG4wz);Pk@Y!qqwo_{-H1=#3KVfAEy$ajv#JI z|6A=S2aZrY-&zy}ArFr1HID^5DUz_%hDar#iLN+sqJHm)bUDzH5iC`+G^^1l@5GRL zxA&5@BK&xLD&cj(a42bxmk8RSm(`k(yCn~hL57g9e^c)zy# z<~ui=Re={z1=|_m8Wae;yi=`pmuHpbRvX?quW(6*0(Lb+B(b2AH=w787O6u}>UOnK zDlJM*Ytp~Q2~JQ~or2KPaxBiXkIvxACzzfq%_G3%N}*k;G6Y;pV?oY4l`K*bF(P#i zHTC2Aa)X4|OufV~>2l)a5_mZS*>MmMM32HFNHdm%GrZdc zGou8h$qR%ElmnjaW~aGGe1p60&W3W6;qC;J6h(z=cw%MnaVc;k!0~Y^1jlrNXl0#- z*-;K0p?GBZQS%T#={5@1fPP#p_Czg9_*|e4$4yM{9Z?|%I5L2htX`|PXq4iXjHq{e zFTI-9*RC$t(436x>ua~)zSa`s2~)t5y=;{}(3B!i7&^jiA}Ah?z^YE?j|S!#%m$@oZaJBsikhZ3U0 zYC=+8*RX-&lQUc&<0QM5|rKi=$82!XLRi}ZcSAAd-qKkFcScDZk ztUNR`a6E0)@ND9VM*LiAV}-JKr33Pp94%mKB~Y`oYzKHR;kvDG7se;`B3GLN_K4np zn@d^$E+3uJtdV^A(0=lP#pByqKJhKRZU>xeekY*`NmsqjX4AMkta{cP!x}5X)zCuP zQbow#Rpm%Uz73mJIum#li!9KX5b7p`x(T6fLZ|}(0C*;Zx(T6fLa3V%>L!G`38C)P zgix0U59pnm5b7p`x(T6fLa2+NMf?yGLfwQ=SNs+}ua!6dl#DTYVMz7E4xsr0Cxp5Q zp>9H`!?`#i)J?N(G9lDW2z3)e-Gor*CewsaHzCwb2z7taggRL91411PsGLyO<^wGK zAZN{uBh*3KA0gC1Zwf*kNrqAoSgZP=_>GPNE-3RYN zrGwk$y$(?6Ad5*WJ4B^}=(bO#lNNRt8Xe^h(C8SdyV2;1V>oou3u)Ft4jr^IICO9r z2OK)dgcK}1(dW<+7CgkEBgW?thmK6c`}{dB%bw?Tkq)03;mtXcd%QWxc>~^@0)NB2 zIk|kJyt&gEC<7@PnJ`r14h&yS?+^(Ayzai!@CgWC}^C@Fi{R_*4>D5#W5^7#|Gk;8J&k&a;;#=apwU`j)DW09DzZ> zl7kvK#FBet97~Q=!TSukP>hou_atp5k|&uubBGm3yu%19PBz|v6{q0eK~|h3zEM(~ z5!^B!2c$S>bB`29IMOg54#p*XxZmcctnR~nI0lzZ(m#IEDh}*g&&0tMf1gt`!tG?<_G*3Y?zAC-cIo!O1en zpgj&z;2s%Afg@k=KK)G{xuCvLQJEl|QhMePiBpN2WL8pZ5_S@W)#eJCUCOa)ByPe^aEBx9X*^M88tUV}naoqv38-Y7g z;z(MJ&y}M4WH(Z4=VZ61Lp=_QO9*8I?egRNDBm6fA;C~Lh%zE?kCnIA2n%SJ$-|x~ zV2h^h_1jhBfSYw*L?D+6W%X#=fUcBy2x#%j+Y)9`j1W~qE#Us+@; z4MMw(g%K{YDstfJk~o`xZg4#i7v$RHY?YXsbc5&?KNCa3JOA&M0@Hg(TTr2NDm)bX zv4RYl$M9*@(b0%NS-7WIxs2FLBSdT1ec0I%V%Xt0p_JR*LKTzc;bIG+2Mp^63)`>1 zVMH%c>U9DJ9)L!l6s%*}YZZ*e{2Ay;NJO!!935YwQ;8U`W=kBlzEDxHfL}`-u@2(C;^egOssH3R6_q6-CDYF%RC1QS2DA}1>?Gmv7xjv+2s7i=gY236MYNL3)sCL!Bf@}&$vS*mr zolYiIPfZq7P9k8GL9>0Q1Y1sv@^+yt!@8(9fUrcX6b+aVDqrEfC42&GRhe!FEx#Ls z#XX$rt`pUc<~yHYE6{wb0q$H8cRv&`D@1dx!iG?%$r}tEH|T&cVE`Zz)_`pK4A5rP zTp=!pm}aNsK7ucZ$Sz%AJQ(xn6Y&-$wRj@H+QrRkFo@e>t`ZZ)83Jr7H;G$uEpV21 z+eL4;iLZ8%WwyaK(M(G05YvHhUNz3qBZersP{3G)B_=w+xYS-v%TyP?U{9b@OP7v_ zzAncJTmCZjJG*riVn3K{Z`?9~mNiocw>$u^Jx#1*)oU@sDW*;Z*#Kdfb4;!r zd4medkkt|F5Lon#bzD3N`}no*Df zu9hcgG|DZL#1Yc#MWQP*{p;y8-xwaIYo}W`7FlJN7~Hd&LrZ%Y+cqK8kFm1VC3VuwubQ(I)~R zDoXl9JhO|N&iw@>j<}F)`0E0Fd=ivm$l$V`smRHYj?2k_Rc+6V$;l8pB0mr>5x6g`UWBLT$g*4(oW(Mw`E_1?m90947^gX>HTF?aZ(j6yy zh|COpHdGR*Xg5@v375?vI|K0HaCU}MG-RXjhy6={mWtVRz<^r%>Z`mP{|a(AoSlLC zMzS+7){&h-Si=1kJXt1O^I&#{8E_*VYSR?~4^k&PJlO$kLqLS6%LJXB_n4Z}Td$YO zYVU>5klv-F-Kb`331iWR(_V!r3{Q+X5ET+NY4|EWz~rWLX-m}dQT0v-Se~GPW-*k# z?Dm2Xh|CMcI*JAywC#@L1x_OQmnUeLWhsDi_YyQb%~#@tTTR7h$}tt(((a<16Of$t z^%FEWkaQ^}9Rm;dwdXMjn20<@f-a&+k&uo}k)T5XyM!R`&h*rtw8^aS1ho%CB!3_X zGVrsYDQ)Y~0oEjiO4p#k04w@#FrdB;CrRL!lqX5(VBjiRKJ43mk_6ON@+1kMqoE`T zSVIs^?@eRxn&1jA7)_Kw`O!oPv?3BET;x()bOhsG)gyF8<1Q@Vg&ao+xR59zZ;2s= zgG>p47;)4fTLQLze6|D%B1iOxm|=}Gut3oZVrn);BdACDj&&~ zz)TFXB^Xpm%?f}dG!-$s0>OvdjnXaKGt9(#iA%Ci*ck5Ng3=k!grLIfze^hbp=pqKE#)J~4cQqVKl@CTw76*v-Y zU{&`ChK4A|CBy4bB@bcz(6zE?=gX`@BVIvC>KR_sqv~kI+U@1 z)B^`H7SJ2pFeYOGWzAR+;3&^o(4>=*vtU8j7KsRowZKz#JK^n^OB`IiGID`=&VpGE za)-T~1yA#pP}p`?1(YOaiUCv)25pzX(g0`tj);$BL^o2PxBL#%q;vQ4pXdb}TBjVw@ONtw<%x1>W44NAqu!n5-E~|$ zjhfc_0ne$d@^JaiYd8_m3ezyYfcytosi~Tp(8mqL!dq{Zs{#no2wtLZ{Fc$2;1Pf# zE#@&1!4Aq|D0k~7N5K04Si`>J6be^FNjCy#ZKj@(G-XRLSIwYl$=5065V#}xVC;78 zmKxNRl;2Caq_kcwqbEmum&%RUuA~~2d3lh+=Y=T6CfXSYu_;QYmM?eWZ`sGfPsX_S zT9J*4hSjRNR_H}|VQ{bDmfNY9W0PyX+}qH$H;+@dBS9CD(s$?3T{awS)llo$ra^+0 zSx{+`GSn(k)XQl1gAy{O18dY0Qo;f=-90Z+11qBh8d9lGKTURq&or$%YkM6L4m9br z^Xy9%HW8%4A8PBSb#P1_A-e6fUXBex+E!YmqZv2_ZxRp)(GyyXOM_N1DUN4v9iL8b zz5X$3qTj0PDVAJw(QzVSCH7`(>K`hBA-r;`%qAcN1!iGrvIOPBa$OZ5xqR@N2i@nfdDh8HfVAUj5&;UbQG@4(_%nf#$g?Nq^ zWpJ-t;vifqJlU(r$>C{ABSjF~Z3@gK_=qy4x~2s*IuUdc=@?SCHCtXRY-WY(&1oG4 z?&DDRPU)R1=*za6m`rZNdG>JZ2L6sa%Qy)Ng}R3G1&|sRzoU$qVzurLCU3r(3$h8# z=vqQ!cE0%?K8k0Iyryjc0~8SO@1!b|ft_$maeV^FnnHsvY^iRIv($m3LQOf!?X@4x zH|z(R9=TJYPR(DpQpeA$&>Zo0)dp231^&o_D^{m)wV|4UowLewHWK?``dfp(iB z8JmDGzz2A;rg|_=H)Q0{^{2be3!U#Zr|`nz&*`Q5SKI{+Zwy1%z_rpS;F{Xa<_o=> z%BXdYQ$OS6y~HvNW}MwK{-QIxApwlrhQ^6^RPZ3w53|k0s3OrQOao5t(QTqEnq|~0 zabs=jCF;TRRejiYFfcl6*xL&q($utTky);ApvDlW>g&Q#>P)Abf*#C#kxAQbbX-sP zxeBrAM;oh)bb1P;gU_bd09F@cF{QLh^rnE^6;G`;M7}~G?%!c7^b0wlo-6_KdedB^ zW?YR4g&L7xPm=?Yu^=4*!5bD!!{YXvvHPff^-AWhQNv3k)xlU*Px1M_+~ORk90()i z&Unt==wNuT#w=70VyV?ORbv2)T{OU66VDBHrjvM*7p`!xTy;RVkp_=>_V6@cPzQ)j zG8Nt2^r`(4y4T>hodlXMvD`|ql+GYi*t!nHm(jGx9qQW7THqC?hrj00&3U2o=*{DY!=r36I2~bKA5jVurm#B%o%s70|6>~|v5;)TRnK83Ia{RPWl$5MY`rEB zy-4GE9;adGVmZXUYq+U;D*^lIu(-O*a5O|>^uv=Cm3do@4~0xy#+<^F;0)K(<`7e3 zO*#kyK_QlyfT!wPV5{C*2TKUx0sWu~tMdaJTMS86j2lK+QWZZ`MRjp+l&(_%`^ns4 z8IK}++QqZO+Q-;p_IPqnpX4m~MANLH*C9fRq^)ltn;IJ-01OK^;tO_NT!lsC0QE1; z^X2LC`-*PiT@o=gwVqhck~Y5$+NulI>biTvJ3)~;=td*k)*c7Q12ACQITW~tRFJ5s z(IAC#!-OM9OWh_&s0j`mfQ)4kj57S(cq@Pp6s@et_ec6##7lG$m__CS;n^_|g_s9a z0o5v?iTw^&6s|B;b-^@fd-9oa&}jnz0Ma=Cpv$#ux|4)+fRSF4{U=dcuiAXrouYMn zdz4G*12enbaza@FD1J=r} zu`^WwjRY?JkmJx;6PN(d3LhzNHr_y7w;+WnW8y=FPTa%Q;#MKHlVR1)F0ZY3*TK0T z0mX4Vqsi%MTIRx`=ctCcFRG)5TYii60o^Gy%@i0`br>zXUUwZ|P}0R8hymsTCxXfx zwt%;naNAKKs1FS}|8B&l@9h(KszjTt=LYRFnT2dS&x6V^f<95NHZd^MO)V&fdP=!f z-6Gh>qQayZW=K@!W`w|rt%_b9CjxEcz%vxY1d75EWcQB(Pu2o%lmd9M>z1j@1j&bI zcxAH$L{=5NBzLedk+@HGr!E#YUqg>{%AGwWRI#|&VjF_NMsSpE7Hr!Ne|cz7cb?-S zh+5%&_rq{hi3{8jRO(bL7P6)6LzNoZ+XmML>aheRfl5Wq@3KZWa38nnRYF6G3NkV{ zB8*1+YPc)HPNUd`?bR6u98miTG{hcK>=IJO91g$>Wof4v5%VbNjLId1mxN>Vr{O@l z`4(GjT5CQFMBLEQdJA_QbVY6fXT{j|!2{&}X9CVkKw98DhX(-K?TB!d7zTp$I=Wlz z#Z-q~plW4RU~`nZ=q8f^hG3Z0JT5)O2>}KLrU8hhm;<2-jwafle7vw(Db#B$ZSa{p znc^*RR@0K|$~LRC?K8BIgVh8G(qc>l0hSBzR|{-PfbaloNZ4+sSFDo_SvwkAT0$S4 ze$))Ei5c@QR?chdoz+06#ZYOZ1C`UX(-p`)rdpIg5#A#1`c2(KPx2CJGi?rG=@eIp zLCJ2lb_K^$X{{a-UW`oD6)0E*uH}@vOE?Lg6VgUx)e>|+Ob<=xPfT$(!14sZFHDQ5}NDqxrEM!940I!);BJHQ00!)&Ew`iejF<`n zaG?rpxeR-XKa={ja<|;M- zfQin4AO*=Unhz^zlSB+CxiNWdH!=lVf~>Z4rK<6Cl@5M}Hghl&{&xD13`gPnSyE*f zlL|-;7`0hl(S@mER+%eZMAE#Z&vCEt)j46_#@cypy}c6Hv?#rl*&4%GD-!uc~CHC|S6{fNHV%s6gC`Rg-+8 zo>gGzu}e(TZk=F!K%jcGI(#>A2NMyP5TB&r3IsLbDq)m?DHE;|I8RUIgsU{+Dq&>> z5?JO5S4nt0;VMnIN+f*1hC;sq8SPf;*j3(Z03Br*R!LypQ-U-cX{yDCDm~`9B5wwX zOrAz5YW}1*vv+U@vE?M_5IqJPh)V%{GP>Q2TH%Zs6N*o5DeHI)I1-hMGxg#~;Fj7Y zZ>J7KOjp*xJ~_N|PqYYFEY^BA^go&n%IhawC9u2r-3%YCj9TxoKS=NuzwWrsLMN5*oDsCtxR z1TlM`W8|0~;1`hqe3)Mp5s_(Q$wj?_Pp1sl{NC`E^;MfZ%M7G*F%MyN&X#RRno^2}js z5qm@>xEaILB8kk0s6`(;L@RQu#@!|v9xyo43&XUcoW(mtD}qQpLMt-PNm|hp<7q{n z!o8D{6>ki!=#fK|BGXs*DJOkO5zO&1lp^+`f>I>2=A|-!F|FI#IJqA&{ih5jB*3MP`l3;dh`Hai1iwLAvO#T@rCY5HXi(q-9n+j ztN0l2{b6-7u^Xp&n$Om}c%Zn-t zH9vQLiGK@)`+0f1$m@gs&%70$9Qte%EaQI<@Y?(QiJ{MM9S6rhH1zqwq0bNT+57GN zSLTCtS%1C%nYY3*KkxD%?{;nP^CN!dbq}xo|L*;#ukL>t3<04w{CDp^{mpp4?|tu8 zDBSn{!~J)Mt^eQP8p{8(g*bTcfB%p3_ji~2@9X*d|M)-T@Bd4@74H3C|F``6zp~HN zf6v+c{oyzA_sjY9_x-Hn&-q_`Ie*{CE4XjtU*+Gw{k{DC-5<-}|MmYI@BFjzfnO;s z?(#=-7i<3)@;Y587U5(Uy~opq@7>e-%&*`6kACOB{;&Rv-}!A1H41l%kInt`-~JE( z-aWs%`FDTe?`-_?-}CVC!oMzl@ch-6U;64-zx??h`^@TF82NvkP;r*5W;1`2PA& zb}n`=KD+o#w_}0F3;(=$%4XfMpp%+1d)T)Je5#|r;f@nolY?rd{*ZhmpG)jHoj&)w$EojW@_ zKY#IA%Y3}>e-=+%>dc)zedgSS#fuluw=bQa=dIP6pJ`oK>^x_owD70J`h||3}YXeu0*?FU+?twinv-7g`rCUHnU5D8+@D8-0 z@iTw(=Qh52ymj%S?NdSSXiH&zw0ub8hbZ`Gtkn3k&Dw7tWnMduC>C zuKkQx+_KlAjN)92>TU%Gth z!a{rT!s7YavvbYanFW?mztqv^F3dkYJ$-ua!t&>yec|$a``Jsah4Zug9?o5uUx-Gz zP*$FqnLT^@^x3)3z5U#?&(pFCi?j1AmJ4&|7ZxwJ|8f@!>IlU@FBIor{=2{NAN(i( zZ+@BPBYFMBv&D_2%N{py##_?fkrfAn)-{L+`c z_~V|**UI@%buRzNk6c)|^rK(7`n9kA%xmjcU+SMMoqlF!zV*!I<(Gf_i#LD%XaDwB ze(I}Vx_aG{u~wdV`TSYV&&@7={!6d^)L;A5%JTYG*FO7c&&OK%Wcw_W$h0l2t$p^# z*DsuZZn3j)@i~>t;*ZLspKQ)F7nu9Sh2>9wZgu&Exw8!W%>2c@KRdOhr{~VjQOWs* zi!Z$N{PVNtXJ^jN&hba)f^|Gr_!q^K?K3mYGtAY(0zd8N_!BihJ2TU~=Ui_w)?}=e zi|yIw)6-2RYH|Mjh56Y_Tgyy|)UA#}*emPdCrZ%r3N_X`i24Y@Kf{%*@Q6 zon!S}hz9vbW$FCv>8GDQbME{nKihfknc2lodwy@ITPVBfT(r`HJBnX1&APEvg?~No#fG9-o zz4uNM0DC8goI-Lqz0atQq*1jLTXIpH>cn0rSts5kdvmidFdR8KH|N}Y?yvjfo+J6_ z01rQW^?9G~EuZLV?y1l8B@VxyIt~7v{h6O#N@cHz(-7=%?D9 zMW;&4<4J|%{^-I*+%xsR319t;fFJ6oIh=lzXq?aIGT7s;)y=-AYv2719!nw(aTPw7 zY-HSJlQ4$pw)G9l)AhdrfB4r}43R`(TdZlTMlDy3OSvqOZ@xJAG!%hgTA zs#`>6aQa46>5$%LR!<6L=Bc!Q)*bkEeGTxRFT+#EYw$0tJ7;vmpQ``$63onPUO2pZ zNsl^Sf+?pyGre~0&b4LXCriL#i=_rnaW0=v=Oll4yaeOC5h;@_(0JmWh}Zwg5=>Y% z8q=hZqgPD2&0bfu|9A=1TmeUA@d@}s+1RAP;?9QUYoy~Pz+s5QL5|F2lZr-cCNX`GW?Ehudb|Wb#h?kq zLAuctH%=}8ICSaa>eV|}mwvqlY?er(cNZ%8d@?8g`tcgbxKatdpQm=m++mOJ zlQkH(sMSV=fUT1&Tqch*+;_YNN;Z$JG<*3x!PuxmZ+2x$Pu5`g7sNrcC1aW3F_{96 z)Dfz`P}Kczx`&vERalQrspd2bCptguJC-_-+XJxKQmokUoGaA zmMgQxWW*hE8>YTf_m`)6(Ri$|u)MLhI9DuX<6gVVsMTAo4&OKG{`BjVXl`b1p|ZHT zwK_K&i^P2XP&gQkrjmtRD*v^*k6Sf`VrF4|b8~xVtq`+%ES^AmwlG~thn?;~rZO}A z)w&i@j==;3Rx-MFg_io*|dgc0^ zTQ~2(P!OK`?|$@ykACpOAAbDK2XEcKaqHTpt4D`NXD{y`99+2$L*cHx^T7u{`0>X- z`sha=zJ2fT+J%Gbw{P9PdvtDhXJ>2oV1Mtcbw6faeec5$KY0Jc58r+5#W!AmVy9j3I?2R z3+X#wulr#nv9!KioJmEK`AR4p2xrUXY}oA&$D^5K1om1m%xj04Po>MHTr#!1koCe2 zD^wQKvzchhX>z&z0iW0J_0(#O(dUZ`b{a-f4BfEO|nSR3?`xm*;0onMfoWPi4}PsB7q1n3HgFX?eL=N{18q z#YiL^ot|Hujs`-pRH|4kWU~2qTHN;4y6=Y*a15jIxpK-GFBS^(%jxoTGVisy+&+KM zHwzqv|YjYdU-C}8OI+Kkh0$!Kh>GZff-rAg` zlJRsplZ{9H!FY1}!qL(Bvzv?S+dG>ZD>Kv6Gv(<*)bDgT>^7U->FM|nbMo-jN3TD6 z`>ogBc;%&6UU})p`MhHS3$>d6^xa3Vy!-BZ?|o4J$0y$XAK!idjkn);=bcBl?yp5B zkT56r@7}&~_wK#BFTeiks}EoM;QbHYfArexckVxY_~zTMym-aj4|DRu>+gN{y?5Sv z_~8Dl-+l1Xg9mSZ|ARMQzW>6#hY#O=^yrO8j~>37<-(j?zj5RC%}YlQAKu)*{?d!D zy!F-_k6wQD%{xceufaE0FI~U&;NhcJ?)lI#C;R6Qu3m;=P;K4-deg-MaC@y=6Y^lY{dYj; z-~aHVciw#c)rarC@zM(i`{yp4Ke%}7&Q&N#_1FJK-GBKvC_4TB+`rHK|9$&^wIXod{`o=#$)o`k{rK7zIOX%+GMi7Syq#hC;kL$)@&^6D|m+F3WT88g{ z3sFPEsZ(%a?!*ovTYGVMJcWculQG0zEEZ9_Km(F1A$K=Ii;XQ!&7EyX3hx2s|& z_2R+y#x}U$0lb|w$fOZFv4V+lpqW6T(MY2Z-$v|YHDpfjl(dcWEypdz#N^*r$%_75216(kvY9*%cZ4;7>1jO%1c#O0=gH8$5OFFT;4dN2Q!52Z6lFo$LMykg55tx=sMK^7xR{8xQ@4X^>va5 za2WcKm`d!$V_<9G2aeJGT&{%98pO4oKGg(mw}A_;6E%p!bYXi3m`o124?DzSQ2MLr z&)0qb#F&UJXti)}SYbPDCrlg0&X35U%#@knso@Ev4yTPp@ZBXxK7;Aui3 zjofc`n+i&G&07X@DCO|EbT<6cJwO>C2ndMgRs_Bq{H!QEPczWX9&;E>Ifv!ADNJ;Iz4{#ldcR&7^Bt+E@OCTSVZM8a70W~ zGpfIj(%XaSQ!p{T9JOakYpyL&bz1O3p?+di0(*$7SMh{>14LRIl0L!1kyyQx1TsmX z%1=(HLdSh+6^kVv%Y=}@9n%uQ$${)b;*cYXUK|nE*Vlu=iTH+)(QZFRH;xJTzI4Jo zAs8P-w7~HhA_`^&i|HZ?Xq0hnL8m`PKRe3h=;uv(rN}_0@f8)jQ7W>}qXpZYANmC`3{Z0o#SbqqH%-GiZzzj@v%L9HBF~N~tEQpP*q{ zJKN}(6rM=M^>z&+FkMucJra(XV$)SLoZ?g9Z)dWFR)1a#-Y^`BGs?mB(g%9LKaD^# zwf4Ae%AR%~gFnZmvAG34BZ>zB}#=|6a1gDsBB*ElteVnlrcwan?bYDoy?VXEA%JX-^PrS9Y3z>P?#A{vFp7VGs2tJ60#AKlwdE}uQKwpyta;%!?aC&X zD{G4&r?)m&4nn2b>Nu(nXe2|zA5*cJVvR;`sswTyi?a(mJ1cXgwYWD}LxF$tnLeR- z+^8G3sbtn{$WdILE#(#y8(VYJai_hu(p4ws=lf-&Q)aDMq4ap{{&XgtDCHN|E0tIQ zP}Sf2gStQeuYXjN)#LmnynI~e z4u_pFyFWUe^f_IATaCxQQ}^=|UpmD!sIB32A)AZ&^Fg=UXsiC+b9H}v;#sCtP#U!&6jCdvCJZ$L-UyxeRsE-)ZDR6UPB*qzf&W>YulwIg zt={I-ttU^OYVINR_x1EteazkM?bz;~u6A5^PY<%%M0LO6L=c^N9t?dmc#zScw7{m~ zdm!kA?(FEr3}BuBza_z(K7I1}lc!HNHn+6*;=0g;9>Nf=6APg#Gy*?_Wma3PO07v| zNByZkud1@9w$=^|9z4Wd1PY#rB;s&b1RC923*5l*X&l3~L!9E|$%d0HEv@Ysa(4>~ zi^B~OalHd*0t$oasyWsH$q^4APC<)cPfxeEbff9r-B_xU$Dm_CYxU!C=*QixNiEsf zaL>t;px{n{*a8kEVTfqTIQW2vsY5JwKc3!G6L3}Rgib_mJJs6O1lW^pL^6X)=)mx0 zBYfS2lp|))hx$piXk=AtjY_2a_Qpn#hRvr=cfkhdp?e4AV{+N3c3j9EWeyB+B-I)C zLf!v7(XX74vb!3duRnF7@uy8)LqeUD&`TH`l^dt$Mfoh}ga zSqv_lBW3oZkuAqyEQ_lc>S=5|`8>$Zrc-D_e`g;ajT@GWX|&-!5}8ivMfKJqOI4}G zr&yGv=7#4Q8tYH}w4t@R8;Qi@hz!0^#2Q2+(L@MC^s}lhSEZJe$!!<)pFH^-vg1@k zL(}QT7KpR<4GauUj+4m*9F};xshc`=j6OBWV?D1Vbe}xg+=PJ_H|g&!$D)WV7N0l7?`H@nCBxWb^caOB;qXR??UZ&zQ){&|NGxB9?d%?;30Pbv%VHEw zO~?n2(R`Me&0-n3L^y1?_JQ8k7IYVti0DA#r~+_~_L*F|%!ER5jFzx?92yJ$LG%yx zIxo6-7YlqUpY7Hw2DX>`k&9i1((3yIjyAp#MJ z$8`4KdQ{;lt5+M!ANS-0LrSA_CMBwfPDVwybhOgYNgQDqi|QnIp*x31%mIHuAF7Em zIKd4<7K6bOSbRBEe?Jo2!yaK{QM7(!8-xkF87gzwq%_BC1`WWU<5F1=QW1`rwQ^pc zly0Ko2t*SZ(FrrzP2@Qs$eFSEtNo}dCnk@}U`m8yv4|!V4~n^nVTnY^9H0$gi2cJB zuf-kC*=n1l3O)%!grpaZswKDqK2OH(XAo(|qJ5lBmn*d1X>WeT=c$>Vs?4I+b5HPo z${c2KQ5vyO#u#Hrts4Q8!5&S`o+;PQ|7+e>QyNv8?548VJRz0GhG153QXv-_Jg(T1 zJ)1v!VQDozx3anr$X0t?m04r~i^(Q1M`YvD$qC`8%4l#IXV&uN^|SjgU)-*2EthJO zT$NcYHfw}|AEA*I6K4^!kU3Y!g49P79v9WO-A%+0J{*xK4yn~Cq%fK{1=m5PUj)FCQMFr|@O z9Ny`<$nIuhX>VtBrCiR1ebokQGOM38%oB14So~3qeB7+{FXqy*rNyP~xy8apE>asy zRc29WERlfKCmtJ{m~w!+TPm+CEG{i=ZY-Sh6>D3nDzm!jV&15d1$?B`=u7_W`ohfo z_BMndmcveO4FwA7X%d8~^lF(|E;S~7_QKN4Y<3~OzF8?otY&MqV^x{;U;D;JG$yrP zHsNrXeW`RRKAWqomS)14u(c-9L1uk!P@tJms^wa}*_JIvGO46DP>zPecDJWCwKbW= z5gFB1jly7YR`T=NM9dp=1>6C1$Wi^Jn#>Z(owh0Elr6QK+4Q+=PEXNgGY7-9CLYVI zF^^7X&{~t_Vj>*!2NTJF*Zo)#)@0U656@vA(|Ce@pWo$o2b~_L!&p$Sl>QW?WY@-cMxKX$HT!v9V@I|8HcLpa;zMb0<%=bQ4HK$i;xzepd(ZPj_b< zrmMRf@npYY1x=?K|LD0>pq(IHrbXC94tzQ- ztsOm>4iv5%N5-_HkpS()lF{^Pi&dE=r?u6e{Q2|GH9+1_TW2q}vkg3HgIGKQUyu1; zo#0HPJx2cM)Ci`v0c`bi4Gmz$+I#!EnviG=hK$Fc`cODXOX{d;JUD1Tx%#oX|Kb8SJh$`Fum;=~Ep@ETI>V8W`d7H53Y& z!6IR)-8Cm&6)T(KlUh$ULBhhR#)cMrKXnM#*2|HKxl=MRLqMgHNralR0PHs;A`z#x z+T`h$#?u`{KA(d^4ag*8qf*T%mnot5k=erPxT=IloRA6W9nb#}h&_;H9b~RXjO)R{ z(bDD1A_0fbB;$Lj$Kaise|tp0 zPgc0Zdmvkvh!bvgtH7lO>{)P>7~uFown)CwDhAJoiVZPM&HyiNcZE ziC7edJSG^T3=s)^ltIYSsc8z3SreQ=lY(&i`DdSRs6YAB=i8dVzljC+7n?6&44}Gu z@vuOo+QL<3mWax16_B2L?pZ|J$>*Pkyq{LMxfA>P1|~-O`f(UEzNxW`qOA4<&<$YW z{z#7NdhR*U{gBZCY6#hd=^d*2oJOe#)Ie`f3%;+m%>muQ8>0~===SHUTN3JZQ*@a7HE`_at}&%+v?YKHueHpq8Fwf7=PR4S{Ffa;@D z$u+kfpru^MlVUTvPJutLxv{yqrMaW4zn_Li;%PKCo6IKCxDx_$&Efgq>V7F5UvM4;J1bbD9-5a=U1-K6I$$7D}@Yg`(i&S2=7aGmJJwD$EhH=#NP zVeccVYI3lTXmDtfW8>q;b0VU%7{heNFr$-1CSiG4aP@UyIw8fU8^cr(I_V;#N)tCe z@d@&&G#Zmjp|H9KAb2fcbayqkV%yr9+Y#Ml0kH>1o`k$J=M!1m&!>otqYTPmKbbqk zq@Z!VryIM81TuL02;(#qk|DRs6;qSPJwbCg>JhP!M;m6S6&xOcgdcA18Xn=Gaa804 zBx#MyQlk=C&G19)qq$!2BfD{wLt%)O*ygs=i1sdYmuL)$#-RxWND1VzRBpAw_+;h5 z#pjGgbW)bMubLLr*v}khFmc@o3@H3|9FH;}R-`7M?2~8VVxyVWs>e8*VbJ1Dt#}@V z+c!9X?ge)#sslxxbjXa_8cD)hepb%p2&SacN&PSz(RH$+0pH(`B@qSKo{o+dRL78Z z%w*P^o{aJYhff<4!)4eZrM9&l%1=ZBnJdBEC@Iz>1dw&P2eMoHZdc0GCnk9zgev;1^9;R`P?zDnLLZG{u5(XMM zOhUA@wRLpSCJh0-+z_eh8vy?{lR|?%%M}@>#yLbW)i8{~;SK$r?Op9%U3iu?U~(qS zHQW7#x_>%B=dfus0k@w27sEV$AD`7p5eX;gL`q*To-|}~n;e0ZSzn#(D(gH)9b)4( zJh4K6A+cFfI*Ep-X!GV#$VQWE+y!@L(dDce-!Inv)CSHJL@B(OKL<7M)4sdB11ZyiyriR|@iDGgp`Ehpi!0fg)idW`+~1sCpD$*rP~h|?Is@E9SkVw^e9WPp zRB3HG>r4XNUUP@{E)-WQE18<7s4BCDXv}{8&;TSfsr(w9W6BxJ&SjR)ZSEeP-zb;X zv$>k~2KDs&{rq7jkIw943&sr!r_O3kO&9aUxr5D}g~jw@HdR~Us?6#ja|X#{G&1D> z>ZgpOmP9lc+?p+{o?Ba6UCxKMYCw=rpGJ!WWbWX=5RI!+jv1`(LOHax7F{^AvA8fj zoeKO~W|3%Q7MDe$u_a2`s9xopg9!5c+`?w5l37WIY7R;eP)+0^8jnLI@+Fc{mDQe# z&Cf2C=N1;$Rw}#hV!k>zRhiW>4A-^^`Y>-ouAG|jrdQ`69cOc~R9FbuJvCHSW)Z-2 zOifA+GLbIswdNLzg=8hVx?ai$&8FH_rzW$A649i7a%yzUYBhNh5I~;Jl$WN9p>)Vn zQzCy*_gBA7=1h)GOv=<#25UMWN+iIKH5>K^Z1t{ckE$wbl*!X6Ov-Vs*hwB&Mt^N*S5?+&!MM$&l&h?X#nie7g5vIi(`xbuYKv1YHG}5_E5HzZO^q;G&KSKlQGREO#`Hg(7VG4*#XPUGXd`92Xk2Bu>6vw7; zzynPa8*w-cN6lvNByx?)Ic>IcpQ*2Z2D0Y6Nya9B2nB*0s+;FME2qc1Qw6X zikNI(pM~*M{nPMEKY@^*Lhqd5_x3=lH>S4_)1$F)C8MFZKOW0`{xJr})C5v$!yp2D z@Q|s7>qQI#$wkv?Z*0NAf2!KfKTS<4weB%Cw!Y)%NJ#5NBN1IfTOd&grTq&*+izAe zeSZ4g-N28;{sO z*wc&bK_hx6#tpVu*y9hl6T!Y32;?UMaze*s-S{An2AQdy{iBl#K~$+x1+6K+u?qTF zDB+P&%^iKj;SoBQi64NtuUaJF&)MACNxe1Xsjsi1loNVxUq>g7L6vi{m{xoT#B&(^ zLv~LvVjB@@;@_yEemSBVQ;C`VgZ2p-2k}(L-yzyMiG*%$HXda35tNRBT2m9IvABUV zF4Sqq=-rS@+l}byL?U{{y3J`85l_}itEhh%=4pcZNHDOJk_^_j{h}RxBZnM5S3p=_?g#`H`op5ED5&pBE6enY?~9j4JB?^S^K^QbOf`Z=fy1YxuTN_HP7P)D#EM?m zbhePr&zJ3dmEM-Kig?(bu5JXH1c9x``zx3R+m_8oqH|l*i$>*$*;sK|2GJ;F7n(>J z=MtW-wUw|qLqTsWke|I#$wu_5p>b_q&geyVA_oaPnE>()VebFLsB?J3K1VJ!v%avP z1&0RB;`2!$+}A%y;&9lXshtg*!=DcO(s6%a^=@>OHULI8q@mz@i9@4aA&dHS74(xx zEHj;t0BO(XR{c^&KV9fDx<@IbK8{n*XMU@8@XLjKVaD&Y2fXQUL^@2tEBvz(jZDx_ z)s2ntKL7aO@2xE-T>+!ZlgK2C=D z4!C3vBU3h^l<@}10^S&h^QFh2KXbYrA$Ph^TuX)QI;YjjS3AdPgH#Hg#-lOoYrpoB za4_XtpUb=b-dHN&(97k9G0||JNzDco_PJ_1U#TnyZahuEFkAC=ih_jW8D{o~i!=eDKd-|LUhsKw|$NocIR(>pvl?`?Fpoc7QG$ri{Av zT(w3?rzzFCaZ}c%b3ZZLSmqF0ARMF$wOA~gHOZuN1!GFNHK#YTpO|d{k4=||#X}>4 z9>}hsOBj5~DA!7sk_SX39;rSt^jac8J(;nd9bPc(9c$}VAIAcLbDiNf|mERARKL^8g( zyX>_*Hrv9<5wU>MkMDsn8=@Z#!L#;ZOE?%#&PLKPJ?`=8WeJA|Da^h>EV={2Y$ybz zz3^=1xG$B7B;C5uW3!E;5YfmU0&<`qQV`IHZd4C~Wyq&vt{miwPKF+v?Pfd{I1O#$ z68gwU6xjAI)Wn$9681aX9(%+?s!h-O<(FH=-ElhvbtAuPNrz zS3w^O#M}Yo>2?xPhgHJ-rC2Ue2pN53n{15T^;Fy6K}`w)k6@+3UK$B6xAxb#cibom zYuPe^MmIXVG1ppjHj|3KkPdNl#Be?BHA*SGwl71IqD_Rbcr zHMYnD(`JrUQ9l=O-DX>?oOCF8Lg^Lbi>$YQM-c_#tn3uHw=P zKoNDfwYImlwzeQT>ApM-640bIgZ%NBF6ocOE0v(yH3Wq)aQm#*=RnH3C^&)YBCu*U z^oM$_GnNadTq&QLje~+J=z1rv9W9|w_)NWsp>ghGvu!cOd>%{CKO3-&;gR^RPAmqA z>P2A(i3X#Dtdy~99kTFpPOi7mRZl|W8viX`byft#P#X@UI~Rt?MHTunRGNR z=m}VDgE0{HI%k5#mF2LNIm8%o=yftR35D)#=~I}f97+x42|ApPRC=kHE;|%ZJD@h{ z)C!2N_8?l@iQ~M{$7b86v&6i<;*HAuMtoAn;%apcnNr%@(+Nd&B$CAVY^^Q5Bb0U8 za>;BWSDLZ$lzMB{BIII_ov`M_KIyU9_QjL4iDWtuoZZaL>nEiqL&;$#_x6BnCk)9r z_{U~DVzBzWPH+Pku9wpRopMm7%1+Qxy&c_TJa>%$t;c3ttF}1(E=wwwUz?wwl2Qk# zX0KNa;iEn>kNHp$LN#mw-9^*lY*FiCj7m z@_I|z6(9I0X#%IgH8MmbvF%el#$&Tx%BM4Vx7FMJlUfao(ecSK zE_r~@9c8nhm~E@w;&&u6+2usQqP7|>Je5O6B@YkLs9Y+{Wle3suJW!`(ss8ql<-+} z6BGJTA%$dAu^2+y|vbloxW1GAjOa%j}*^0;E zG#U(Qo8RFID3nt|Cg&5g?eY4JkoFw3*tN#6$*K26Ll(7G4X%+-%(g8R^cdY{i`HUt z`Rqnp4)R%&h7msViP^3M3&m&xESJ>?^$B|C%EolSVp0=GPt0~K9*hM8ZV#l*IPG44 zc&;?>5X0t*umCwXC0@_JB>C4Av+XTfMX06@n^4Y@1Ski4W zjSKlbPt3N*VhE;E4oAr4usJ5jtz%={Pt5ku95#z+TpELhJNd+HLySVL*Gr_1iE$B=*7D^iW_!v4 zB3&gJl@N*ehCg^>wmnl;nMxsN5nF$4w%yJt>4=Ke^J}vW#aBf1so(mw+5X!9#ccN= z(0!DVp+Tuj%T{V8sl$^h&6qLc)P7>N(F`(^%NwBZr_g8=L%{&Q^@x1JlGT}5Ps}!- z%cKegLNerp_4eYy^~4oRIeKu*s)J9=HebXg)09jGz8BJF(KtM*7aUIu#*Y{ODOV&T%kbn z*leSz3TJqHs=ot#gs2`Y8rjkBoOB0bF!RMmELfR{0p zg7{X+V}5M5dE;Uce~1X~0mzRbp&%I3K4cF0Ly4JiDyls;+cfUbz#yGCfJU`J*aq2w z=Apy5Wx&}EZRXF3rIDgs}uvCiqL)kdI< zY#gy4N|zC+E~IQkZ4UWtE|)dzCe}c`eGofp>k>;l(LOc>YKl9El8I4na6%#X=%X%e z74$JgLHdwQtpwblkiw*4`VrmT3aDKznQiKcDTDXeY>!El3$8kKZKbE5eU&+X9(zoKGe8nMb6oj;GrHuB){jkL_e8LY`p)cEZwE=e3v}4*(Vv5XT?CB(rfjzFys@WLvy37I3w9hnx?Z$R>VtPTO zA<+YPy+KTx7-d!)`)5$=QC~P)ANlL3T|&nqkVtB8X9uQVKo>a`a<%HQLAE%GyOl&L z2yxjZj}5Xka2_vAKMUe>zNrx*f21}@gE_UD%SA)+&DG_km5w25y)MBZg+fAfNc2;~ ztjG6ztnBi7lxK^sH#pF^NQ$1ys$i+R7MAq6yAc%C|uAy}HV8&t1 zCQ?x-@;9?4H0HE{%ZBRpPDn8(R&7>wzC5wS43skY{nKmdGUQ9?bh9=?KO~#AgM)9B ziG6IgLplp&)q!6ze|0AAoto$yQD$URWKUaHKbAeh{pMq{tyV%=nbVw%=9VfIwUE+B z(YxJ32uBe6@k}QDvDwxe9dN;lg!Bc4idZ4*{vKbON53?7^9nAdG^XzjsLaouATnRPVr*o1`LPvo$q z3})?q`+IeNW(I*^hw{0lnAfB<8B83xZDgo_Xn;atQ|QNL+vkb8S4t_H%Mpya%~PYJ z8Zm#6pr2&W$iq*}c5)%Rv*0xc{oat@VVbbHG!89SG9_WMd2PpLd&!@KY}uKz+wL&v z^-2qb%zfi>C7;fsJh^SVJwAiYV)dJCQ`PKgttT8Xfnk6E-0wcQZCjH8x4~sHPMHl( zuT^i!6f5DlPQs->G27)pz7R?1Y+8#RxLa#qS}XWW1|^R0#B4`nfoK5kBOp>7R<}1; zo+&scWsDv$Rn?;BpBmgDr%|hQc}>Rg33WCVUGr(=RKgRpZM0ad#wnY_=`{rnk*L#P zkO{cRCuZAa*87u5yFKW%TC5Xe<`D^-(*u86y>0)@W_8QY*<&Jy~i^CU_x}G_{ zZF?O~GZ>qoT{9tIiI@};@e{MH*ZD18r&cav^V!3+VO+x#vu$%Ylv=GoXc-+9(1)8p zG20rmN}-Vp#6lb%d-AJK%(h!?9Ffb$82ILfPj1^zyGkrk&^kN6^yIe9I#pGS)R-1?i&CE(m#K8_0rutH_o3qbLHaI>lZFH)cw3UKh_tURr?bG`Q8<#E~9^JjZzrA;CySQm*3JD?9c3e}DPr{>I+n^@|swdUavv@Y%Y*n)MfU4$tgupSyPJV1Mh};l-gAM&upDNxO(;Q+jW0I^_TXJcF*lzy!PPc z!KM9+S9Z>9Z*HF1+uz&#R^6YYJ;j}a?Zb0-UU+ch`uPiom(Q$jt>jAEXU?q6ezWe+ zo^xi-9iBb9_TnqA+`W43;?aez<&~|?+0x3&LO%bEx}X16EW8U{x%$E@FW-Uh&u%QA z*{t9A=b5E^A(u@+>1)`rd*R0I>vvvy<@R-`)ZAL#SzcOMSu7+Iv2-T>hjl-p*h)}< zclXW z+I=>y9->#D@q5PODRCOvXQ``%97|aqhzXJ1-t>Z_VdsD+_DWDe&a!AkJ8? zJ)!+^IWYS~TlCES-ubP?QhvIS41_|yfYWNwOzPBA zx@r-CC!TZuUfmDA5-?An+uhw*sOECVq0?4}-KqyywOnlsDqOitG94KM9JpThc2_qR zp>8&lg{&)^!D=!YG|F+M%5JfTrWdBe2F>r*eT4A2(z~lOP~BUMrvp}-*<>^uAW2`X z(c3)!NM>zwDPT2!rS8M0AlYeQwVd;X;`z86LULBU$*fkZjM1nk7>?yikU=&Rj{uV8 zvAgCHnRGT3@da%bv(X6ird26Dv1EQa4K>2^<Zy=f;e-i8a8c zd|>Thb~GxTQExPA^u~NdrIc%v*N+0WNmFj;dQ!^avJ_jtQ}@R>zsF>?>x@Q~5}<0e z##-E7@ah%X$!JhBs>TuZ0`}xZ<-;!N@f#NcfymD>Q~^X)YFXPO7-NNuhmG z$P}9R;wite{OF5yA5ao*lX^<2o>VB5(E60do`NC+qroT1o5#neq%<}Z{z#UM+4Q|H z*8OtMXVXnhDOD=@q)}b3{EJ9>FYU8W$t@T4<>{a_9?Z@KcU@OpDkyKqcs|D-vSyRTaE&ae|xtaNCS;i zHd&rh&%4y^$#tiWeY@}1g+4+@9F&fC1)1es9c*k-&U!I*?U73l-;j=<<2520y6wR0OGgaht0pjsX z#RAZH_k6f8S1isi&CeF|CCG-H&7^_Gkt>neMJP94UYsjWPtPwbRHm~Lpz*e~aCu=m zUtC&TfX5bQOLL`CF&hFJKeZa3UMd%0n=dZS%@#{DrMW^T9tIl!+FCTbvXq~#lq#zW zP$)mYFgKkI`V&CapIwe+me&jLtis%CC08gf!b=VLL^3}I-|eK}^?_*^dKtQwpM{$9 z*+Ra&va$>`?oCcFE>6Qk1(msCp~1d3YL5Bfa z&_aA$sQ{232>M&a=<<9C_Dv?43d3bOnDE<78kJH#B_CHQC**2{4p8%v@^ZE4KAQ+d zz44IW8HCZO6mSkFl*&o1ddipp8fT?b%S&_MX^Vvu5s0rq^hd8%E96Q!{GQwaS))lS z(DNUb!iANEQa&3CMf^US!K8)U0>}iN7+0v>3QHmpjr(MPTT8}QU_0jGp;#nr)JQ_#;l8S~I1AiIU49c4c+BBgy&svd3ij zO5NW-y_gKllrk}wFO&>}O`Q6fdJ4!vt_cP_zF;^rT`E^{u^=EPauI(q8io8=a5x#K z>NP)`f_at8t$|pkkSxt6XJ)1=OG)77XCW>SN_br%=@@H?lr>^}}FMPQqyP!1cOpcv3#0 zieEeom?!nw^{ep_fq<)60cIYaO*(B>oly&e*61cDmDcoH*`gOhY$|uI< zFsG9$OFZfc=ye`x#t0>UBP`C;q(r)EOeb#u`fSRl*Qh7u@)}sK2*%fwZil+y8s7wU zZ-zUuQ7BcJ9hK-T6!dT_Q5&qieBvqjiHScOhcdtIs6}Qpk627Wg-tsp^NedYSBsT& zaiT`!l$PD`&X}Bxehe0egBR74_>@=bp6I{pz+L`FTe8O!Octim#*ytjlcHB!@KwI+`f3_ zXz%bm(D*mrc?9n{y!hbi^`o7GtA{}2t>1n7^#`|Kc=6WlOM8bmw^o40(I33})=PID zyn6TM;kgS}b{DE@oc!HK4`06Z;La;I56@lz(Ymz(G*0{O>u=t>{Mqx{8w)_= zgzvug=+#%?nsM#m%DHoA&t2Hr-CC_YSNAVH@4oTo8!yz~{U7(PT|eAEcX99F{QCCh z63}?#`ww4x`_-FwZ(Y3p!u5;$`{&M|-`HK8UjwTCo%h~+`He^SZ``#x6d@8*rGH||_JIDh5*`77IJmd{+eu@5x9^Uhmuy?O_Rad72e zAO7y@-sZ;k*@G*Gn?U2Cx8HgD&3ji4E*&22ZSKJf6&q`t8#_DO=eE~@#uaaV_qB&F z+&s8__xjoMd*}BqpIO>kDa~!1+1Z`}8Yet@_ubb)XFs@q>tOrf@bbagt%dcaN_lx> zIbQ^p{?a>--h1nXE0=EFyK!~z!i6(?%j+v^P`tUZI1f*N0O0l4K6v=>?xky2ZX8`c zJiE8Owz~!wr=10O?`L+p3^eY0_~u)$+`oJ2#Y#s){8ISzkl!M)ms-Y?48-#T3P_AEzM4+6WNF}2sHkCj~>1C%By#;zHsaEm81Q` zi|2MXSLc@&3i*5xEQZVK0L;Pb@57TLx2|40I=FW6($S6cI~z;sX?UC{8V-iwe(VGq z*T4SW+jm~Qd-v+W;r@klM+Z0ew^s`pc!s4Ag&>jF5iS5hcf9rH2XDN5_wJ2L=l6HE z56_%A*q*E8!ihu`6!v+e-u&;^op|{1`|rMX>(-6S7vZs>3+HxLpnNnF@x_4WzzqOT z-IR{1DKC%Sc<0Rrx2|3}JiEWY17e`GkS`=cfk-GCNd&;8oY|`YJvYAjDm-&?2O2vB zQNB7~P8G8$Fd%++03z~p`|$F}sts^&zV-IouiU?N<>KMBbNiJtJb(ia;>6?8U??~@ zGq-ygc0@4^G%kDREm-wiR}Zh=JbSbWwG`9YOgxti#FK^9os|ohZr-@NS;~OM{>^va zeC4&5?%z0n_2}A#t-0CKEWG-XN+il>_Rd_mcyt?{9J+D1w+qO`o3G!6oqPM%R5S z)?d7R{l>lh#g(@%Us$_v2B7b}^~MV?9$mP0Zsp8kdA3warRMfBxw+8v-Us*3olW1p z_2GNh(u-%V05vn;e&fZPSI-|^T3uY2+nUXma-}P0pxMIiM<3ps&R=}t&;R&Bc(xR| z{iV7S?|k_3ooiS34>y-r7ME8m@K(^>!`VbMee>;mXEuuE3zuJ5aainz%uB$`iuWG9 zaOv{-gT2*-rPb}(LS^&D>z6kx$)%b7{iVRlvTwHRvRMQE<#z%6{+ln|zH)eBcWHHL zWqxL6cJA!uD|;7KW)|J^Ip1!=YjG?%Je7lm`#%I?HhlNB2RHXGY^^RWEw1KgX44y2 z_BVIt<_^4D%h~Kg#9*E;xZST+E^mGx&>vKXck#^D>f+LRdA6Lpc;%Igdn<+eyP>O< z3pQ>EimuD{o(0%j_%#m)8#BXY%FX zK_YbT<=x9SuGnGmp5=MG(U`Zi1t+txwsG;rmmk7wTIcdkD_p=VR!A~j+&;Toy`22! z02H%0oeq1ruyNtyt!wvhUcY|%M@P#GGhx3)t5BG%{tO7$_0@Uk#q+^5oC&AXsPVdO zmHnfe2N$Cji!tMOnr$j5?-ahe}Gn5$To z4%LKAJt-TTaQYI(dAQ4`^0U*G->!SVo?gm1&5)?BGuYLT&Xy_{gON4wd`zJk8=uhs zZ<5|Rw$Z%L6RgfVy90aLKUUhil}6gRePP~t-==CR3Ntf0cFc|;F*7qWGuyF~I1V#Y zC6yFocUR;4_ROqKC*A0DI%#jUr~773s*<{;dhEv!{=Ods@rp0Dytpu*O!~qV&i~gF z{-16=`O9ImSt^m3>|qCQisM>|Wfr3lN2Q@@T%mY0m0et#@up!xn2Fll{;+-|QDGkjR(E5*6>_0vMB%A}fEfOp zTMs`oY2^l^QpXFdX5^N+^WD5Tu(r3me{!(1ySloyv9YqeytTc5a(Q{NlTZ09@C8cB z7!B_JU)?(UTZ37pz7Bz`@837ryJ{= zJ6owlI=6Iqae1+~vA#UNvAVRBwgqG1v?*f3H#If3VeN96DKmetzj=7|?B(O@m)plX zTjkkpc)RWG9c~uV#r)jElkE+NN!wVRS)MC6-N}?U6i};7Dr$3WjY+PGtskE5ZJxgR z`s>SQ=exV>$>Q^i)t%Fa$EQcDOPkvdPYSa;JF7c~`wNR3^RwlpS&KKb_jlgqs&fNU1`FR#u3$~xKI-ap+vIbL4d+S%IPJ=xk?D#eP+YnfO+ z7)vK~QUiiJnq1x5UR&QeeEw)_a{~}Q$frDi{OoLYZsuTqZvO;Q*K+f)0NC0)KH6EI z&#bI$mU7u-B;xbxxf&^-ytc8szPY_|u(rCfxOxolsGaA}PG=*T)b{Gq_EyEV>M#C7 zetuQo53erI&mX?HTrGy( z`Td>wjg_6fo!#~I&GnVt{oVbo<;7HNzFdgMvhW2kf|=!NXLNgQYrR}7%q|yCpFTa? zxV}C z?SJ!XX9>{I)ojq}%Pt=7Z0;VdZ+I+F>a+*Psng!Hzy$ty2@#XVJ=O;&dr-%Coo2AXwauCj5 zz^2tHxN3-Z6AI=E<++1{t^K+5^4fNJal1GV;7y=dUV>}2keffgdUSblespm5@N{Q+ zZnZR%&06JBsYoIf^7tI;)O0#l-a0yfv!7jFURr*53@^$Vc=>Lhlw;oX-0u0K)9qEb zD%Wt^Y0;vF#f)c4VyS4*Eu=FU3^rqmLc~oBjcjgioS*C;uAe@+eEjC=@z&zP?8@T) z#zHY)D&$HKaJG<-rwj9mKsf9QL?Thhd1KS3rpQR_I2JX6!obSm(c`oI{maW&Z?AWk zH%luUo2Pqg3)w;}n#;gjF%e6|W8t9FsxyM>*6voy_|r5BW_SoOF)}(bv9j{y<@KYd zk1t=px;k20SzFyc-CZj$6tf|#*YB{&mEM5OAN9FR27^f};qd@Qu1kjzj=1~@Dxte!NJ}_X>KMPcN<)GtC+=i+hNhDRjG7R37rH%DpL^NfhS_e z5kn(57F(TmhAy7HdHeG5`SA%ncy?g0m@Vb=VJFa=I=+A|f>RA%0;v$tHX;!`*(4fy zbbK6%90KRZ2#XVqZeKos{`&Ia-tPM9a%n!FNhEv$pHU?d@tI_rP^=ZnMLez$l%tcA zOgg7}FGNECB&PmFnGp4B|G=V}U z;ILRc3UqX16F5AYK!gaM!C^}7;^I0>7lcRpF~+Ffm~;f@l762TKrS0cB^R;*(;$K? z9F3a*O(bFrNXB6>HK0d_$0pE--d<*YG5F?`ZUX(3tGsV?sO0VlAZZ?l)vA$l>C`F8 zBnE{>p^0M+0<7@9XL69snzTZ~wsHFp{j( zaBw)b+o_TlM0%$}2$Cqq4V*zjVz48SFh4p_(Wm-1fo|aT+BBh{9lgWg&0>fwu8>VgHt7xG8|0ip!{V?A$TNm8-l?&^9u$HC^lV>W%LtwT za16Dxy`#0QwY|Hyrz^(7AavzY(jT##L^DFGj5kSU)2SH5@Noa|_%H%H3WhV>w8ZSx zvC(5>f^b@^2y$tV0oT>t+lkjN>J%LF_G&pAHMwWxab1Xo!=Zqg92p%(2n94Ik}$!Z zdo z=QhZi60SW2qG~ZnxN&}&-qhKtzpW=uR zBe=m4BAXT~`C>dzP_LX6iK(EpVz4NDhcp~j={#v6T`K1S6}48tF!^e$FYdQ`eJ3<_ zmV+cAh{Mx{N6+JnGo_ST?3VDRBy_b&LO}H+SrVg7CY(a6~G@}*j?Y15YvZ)%h z#MM5W0mMaj?lGmquVUh5hDqFcLOv$~qohVhn8x>D6E=am;fy#MB@w(ZVW_YFzb^lWK(eNAc0Y?VT>KsF>33E4vC zR5bGS%+{;(%*^7om)BQy2YjuSmG|l!nsgf%Pab~zB(Lmf_^N{~oiuDuau+Qjc;O}1 zPcrVjoXO%+S;_L-?;@|?zyJQ9fBNZ%t=8VQ`*-ivw$$Ry#N-Rl`t$c$ryI)(n^mrS zILNjiST^_fkDgrlIS!K!Kixx%oSI$x*Y6+A%^sh=|M`)41q)*A%7!l9Jo{{McJ1`r zEyL?`&sK49E@oOjmhI0t7K*R;HB|mIhK6nJAet7-*V&bC9+!j5FP}XA+Cm*^ta(t? zGl_1;on5V6{QUCG=H8CUzGt6ePZJ5^UAud^uwXWEqyEf9|@YfefKKRM*H4meBkX?3ovGM);KmB}=wewsGO7ijhX9?r-&S}gDOF1&N zrInq}Pe|tXPPdf!z{{2v7_H}rS+SgG>ms9*D_fB%{hxb1(#7^z= zI??-!`PIuOXJ;jPv7i{IKC}q1cmx5fh^~g!M7?ZE?~W+M+s{f-=iJ^jj;ehAZg%b> zU-AB5-kv;N_K4VLDgJ~ooPH98k^J{)m^_{8XlY>~=1zjXV1H{CvYA~Xu8_s%@TNp2 zqtom1+>GS?4!2vc)yQ=&n44h51j>M=XXRMHtyA%*2t>9(WwcvNkOK@O`6tFe!fVlK zg&d7c6fUoAER>8~9_YMb{YjZXA;8@m8~EtOW6{GLEO8M0d;XgwG!tA1%c ziWM?eV2gba#BH%SEEc=Tq>)NQQj0(!8ffm&TD=hvc=(piE)Uk1GJzmaRI^KPtYTgu zW5cO<9PlTH^X3CwPCH2Qoi4M{t~NVOLXH^0L%0KxXvpWAJz5TXJOMCPC*zCT%LSKJ zA8=YDk$~F;5(KB;4J?)0WwAisH>{!zCSb^bdlK>|eW7SD($lA`}V){ce}j7oG`(;sro<7h*o0 z+L`o0eT>rs0F~Vi_dooH%L2Co#CaR^VCn*{Qz7JuG>}go^g7{R0x2De=i&f*#ym!~ zITWxuEoM8Uc-!qhr_=3%53t(w2DMJBP#RTQh-p{yIU=4Q;I*4nGM&ce1ftd-ivzE% zldCNzpWE*8=$$5q+ZVCJZvn}L+odzeH42kiQ6W~{`ahHsl}Z72Gav!EGN}r1!MThl z=# zJ|ie*9B_w$63FI)lVnngH9A`;9`V?mHfx1b^}&C!XmomoTp{FwYaQrgDvd&%m_WPX z?g<4#o@`4f?!c&_|dpW=N8>*nq{=s?Q4b&>soKQ;{3Rj>)RiTJ=g`+_mt)S4xEfHjfQ{L;;IMCld)1BSQ$} z=okX&asjQANCSbDj)y`~$ih#AoN(QlRbrJ&2=61YM50iF8-WejEilyoKc$UBBF6`Z zdPfj!rz4Pp1_)4r#VZo35B@fp2*(mm8xRRb8DA_BGRaera0F+J17AEfiNoVy)j+{v zaT5rb+pu6BFv?W1R6Y;rH^lUZL;i5UX1S4D-Ix=(Q=sqWftrX3NCcTgz+o_GJeGtT z9UFt?eSgp37=x)b`r=v8nZ=6!F%VbbtiYMa0mdUswZ}E8a1ZoU3 zj+hw7O%iZWs4{@=??g{4gl>>zKv#gr>jYzyNe9<2Z+ePKB9ibJGzLF8Nn?;FiC8oS zU4i<4Apb{iB~*~UnZzQ=SRMh(QOe<=6##nP$z?!Tl>A0DK%)`>Js?hCa1|KU2VcU= z6^p?l5yq%}B(nHU=xLqWdYAR|$-Kir~eIml7#+@{$jliEx=4P0k3g_y@6lgMygBPWm( zI0T43M@EKV)`3ty#K_?A=qO^aZjAw=m{j9!jFPFTO))pQmKe90^t})DoEg# z#3JF^906e}6y|`t2J%E8vk2yU*pvPt>ew(=AtNJb;q`K!XefC(@L=JCK#a=dFksZi zgUJX{F}ybjk%?WM-2?wRkPpE}fW5X4=K8_0F}lwzn4ZLI95TKUU>B)~$C(E4)Fhn( zhz&F)4UdfU55iR5S<&@D|9|i6?H?Mb=>Hiali<%Fghi7SawZN7S}Lg|D^yu{%qh4p zenr&b$0yLEqeDX=!-b2hx1*!G2lgL!ps%N|A5`7F@E3MrlEtTCZ8!Z247<~(qD_&f z;9-KpVqnKdh6X?^*5BVZj2wpj@9oD!3^xkWrh9vP`upH>V0#P-KP}YR?2r|smnqqx zDga?f1?EpksD2w8o}|E8nm|$n4AS6W*BA~@nPkvjON9Cem2cEc7_Uz|oX4KbMBajQ7D|A?W5*3bq+Q ztYA>3^NRd3GZh0!40;^S8G^^9OivInboh-5oi1!7!zG=OilRDyPA_AkkcbKWf?Sie zvGlr?C5_&lDDun!{3HQUF?u>o#p`u?w^1%(@_FD;Qb(gAo`kFP*}Zm-E8>JG#A!22)l7Ji;9mPYVjVWvl2Gl~bk|*NOKvY8&vdw;* zTq+W=WlV)&5<^J`N%~MwCiMlDc42X|9A|LG$8aNK1STaoFRCz=w9r~or@ zts;L|p?4*CbeV)FV$zo~7$#3`^MyTTmwRWL5#u0c(T5*4G4}y$mP2te(<5IPR!hZL01w$(F9(argiDmpj3D$RBV~hc5DT}j z(dbFU%|1)m#lO)QjdWoYijCf(DJ+0n&CM+>U1J0)iGK7Zb@mFXKRg8F7^a&Oa*&_59$g$f zJ#xVQ|1aV_`>G}I#UTmZtT(^Rn zIDh^qu9;goh?yX6kv!em#fs<9()`l?>WRl~if)@XFG?CevZKDG4a1>~OqJIkIYj9*UnPX z0<+G|;1ENw}^d|5@Pti*ts9(@&@Hn<^1Bx>Ke@D|BK@4#q&oGFHbKYzyH&p|MaIn|M>Xzt3Q2n zb`Ipx>Rdj*w!FT+JU2gA&cR&Xw*Ttw>t~NIj*rd{wyuBv$AA3p>;09*(*73IEX^kT z(Qq~xgLa)2xtsw7IuB0|_K)_?cTW$WzI$=8w|08DRM?nFW(v7zIy$!sdLJOL zBFRK34s-dxRlIro`10iReDCCZ`{>2R@%59PiX&?h&)ybv6#)_ zboyZ~{|}Z|FP}Ys{P@{7kB_%Du75ax{^P@)t%cbjRDsRUu56zkfXaUXz=nJR(mJ8! z$QSsVTmSWcwn6RAqlZ`5=O-ttCvUFbzJC7V(cwn1V)dU&FnF#&$J9!B?_gtjDI1NH zq8^JQl$e3JT>b9r7gqUfSzkYS{_WQnSMScDrw6X$C7`e2pq7^pjxG;Z)<9BI z%4Kp3T@W=cn5%M~`2g?LU5Zxx00;yEVJG3}04WDwd8;9v$yL zxmd|Y;o+OhuctCWn9B#BKD~T-b^P$)^-s^YA3p#7+b27xH3nMAK(A!mw)>4?W;G}mwPZ` zFBOZaT&bKZt;}D(eRIBA$|cjY%caHne8A#N_)~e9%WwUQ9Qut;Z;0u0=nDGBKmGZ; zZ(qNA_U7I56>wUt%_ifS+4;=k<+Ia+auRevD=V|mKb#Clxjd*Il-z22@$~um>FN2| z-s7Kt`Q_*L-~ajhSFc{2UYwn6Y|fSQF>kamJAVoWhK0G5KUqlU!!}sMX585jp9%r- zu4k8*CyyS1%i;-Ko4{X}wRE*l9seSWtIfCA7| znM`(@9F{@PF3%r6d<4Jt(VzeM&%gZm6!cs3J3n24)NQ#`F2(~T@XJ;x~E? zufzE(2M|p4=&4_l`F2K=sZuDFRvs$&kQ3l^hJ|2y4%+AcO z7qfA1)E~~eVsW3%rZLLRFeFvj)~ya;#_bw~OsxToP-QUc&Cow!S4$S>qh0_&e9_YG zW)`X*tdWqz02Z}-aW6}DMDAP8EQ9Zs0V3_(F|2k>EfjYc9N<8r|!27<77-tU~d>K}wBSDisTO+-b&i zHeUt|Dw9h5Dl!9%A)qvVKyUyd0GEix>h>B9dV|qqwmU%7p@wuAiGs%z@HtE&mkWiF zMHcATPhvE6e=kc7jU?shT^bU z(^N7FIiHSY^6^3hXgtq zgcQSKlZ?kxsTdpqpNXfl@mwLFnx9KXQVZo`A{+6#eI6HBRCNle0K6$enMMwoc|1Pr zhAHQQ0g}N0Jvz{WNSrYhjl=4g*3^ev6vt%z9t}t(L^yX6-P~Mo4lr%@YTpHIXSKV^MD)4Be4th@65}Mlcp}8B;>=?+64; z4*f>RkDnsrse~!O2q5?iO-J?9HEs~>Cn1_KmrLY5Zhtfckev*ec@7MTQ)Duo#S!sY zO!}{A|3raq3YsQ2OXqNeA}*AcI=FOG&{j;QD>DCkI$>1%91ww~mIy_B@LF-%)C!ua zVw(!NgbX-XlkmgIF!JKH?@l}v4u?&pGYH(IDIQABfsP^^1V~*WSHbqM9A~m<(;U$8 zgUgCYBoPQC8V!`@I8X{qN_M{a`rBoxHVx5&@zs20Et*S3J#KKs0PSnlE5v*@{A?KO zsnki>ei92(@(BtQ$zup$_(9{Xdxx{{o(ZFBDml3RGCpgyWPG6@Ofv9bvPy*0A`XcnVez2I-zf$S0(N{RV2fLgE{IgZ95$B}3hbWltJHiE%Uf~cM!jp}$TLwVL|UQv097Nt}N zxYLcRmI6dBlo-Q8nTSIX#{tD4powTS204mC$0I84KoW z6(}P`@Gt|5z!Zfmq;#}E^M(qx6y$-v=Zp)?zJ zNY>>Za47SXu={XxV9E4JI(Hf;R*HG|T}o%5Vpdhrx)G6QGNJQeU^}*Yl$>#~pmV1D zCX{`$tc}IbFTKh9>ZUR_4<=?7jY1=+IK@I@F_*{!#0eSi>FpJ8Qu&hQ#r3>radp8D z8S3Iud^Q(cUrI+Uk&CCGLz-Vrn;b6IGV`9xNIB>V8i zItbZ=E{K|=iCL-Of??-qZ*?Qf+KFUWl3I;3$q>RdM-iyXYlZE(fQB6!3-P!PQ900@ofxEeTr%>|s`>Q1JRs_6}d~ z%XR7Vjq4|o1yC$LECa$Ab%%45(|0O}O+&L0Ak!J3SPBW8bpGAvfqcGoZT)%k=GtAjmKAG|)yvBq1@5@);Jlfble7mTf z9O(MhfU4rJ2YW&F<39Q3%{Sk^+lfq!){aZGZ09~nxarHhczt#LwUa>fx&X{{U2 zfB0$s+xPGPboKqaf8HM>;(NP$#xebLpK$I~`uO!v#|tI8HtqKo<`(9q-Xs6syZ7&Y ze7EVDotu?eaO&mn63MsUynlCn_4VVQe|R3g6c6|HLd$_>)A)F4Y5(E3FVjCfpL?;p zyEO0LIZ~dKTrZw|cbV3O9Rj@=H6hMiZhmuk{mV};&)}v-$Shv!@$dufoQY5W=1<_*t{xeE;pr z#kY&E4r6QIf4}c(?d_Tu>Ket7@>-FKjTTn)1yKmGdq zcki#xUafB~CkW$6<`h;#!-bODk9WO+^`))zmFwB{oj>I#d5Ez-XI8#?GIvr)`pIA4x&(@Habd1doXr>0sbtgzq;N2AxA?qit2bOK7M2!6wm@dC6mfaI zLA%`w>^qQ9;_AP=bsi7pU_ zxYLU>0FJpVCP3On!j8YZ8Bq-@K$M1TZfEd@_zVPMvDplGA3V8`iYqnS!Czz2X-(O< z*<8+oD+qAD8DApq1$IRx=5pKr`c@&BV9etU#(_f%gp!$TKABlr&4Q1K zMb$=&UJl1+L&01j1E-~&5Ak4D|7?C?&IjdbAbpfu)KZPXj*hr@pu+ksD{ zQ7l_5&XhJNqmh_5moDUKZGF9+6XS^Pfv&C&%ruKD zl^YtX>S{Wb2AdoTA7d|Ho|K@Wd2eT_2*X<@RuNX+I*G(nseEZB2yJ=eb+wIk4K1y$ z&3)Z%6AT=Rr&f&CR8_YOb;#Wwy;5ZR`kT|8&HX1J51t7p<6u(*J9RWyoLfx#{mPnJ zsAO%ZsjY{y*7p8M%vcA4E}bJ(w;@^@8i~%RMXcTV=6Yf2V0Go>c&7x^b36jFk#IBt z8R-Fmpt0(HWeqf>Hgp0zgc)vWZt3X8WV0kOy{T$cLi9u-s4jo`?BIA|CgP4{lCfko zoCKG1MNsv@kdDiSAY=G|+UhanFbV+yWGyWaX4%@Qd#H7?8#}rOM};{k{0>JFu}m}( zvs=8$**w^Xe6V6fhz4sOv<%i(*WRz{=%|N&gyuTH!+VhJEv=%BEZx`+pIgt#r9~p6 z%^%6-LZIEY!+Z`P%Si40`;h5WdB5h~UC1D6sH#Fu4m8$QR|6v42BpCqcJ@3iAFipa zZMT)hJgG)*2n4blvjv~m?^d$LAV>@%$DkMOK14s>uk7sW5aO%qnriCcXhJ(nHQ0om zrA$iET3JUfcq~GNE$B5y7BZ>H_C`oFgWyBx&AayibX_;qP;gnP z*WjmE+9)vPOU(L)Y%vIN#yXA1f@*AhP*(%l;?)o=23gZkFH&=vZ|&5E|~6yO2|>~scowN zys@G3Uc)$&t{35fl#}3T1QcYWR}qN}napN#d2N;`tETe7gOR!>2s?x3x$3X(4DyCh zh{kG&$L*@T)7(+p+&e}cK{d4zaa4nnj6u9UJ*M!AD z>H`l_P>GWr_rAFO`F&_0gyUF^9cpM6)K*U5WVvMtf=mJ#u3Sa1X@VXnNLOcOqA8Xk z8nmaJ@tEHmfFuDN z7Jnx1w_E*zd;(-N!9>(g=<8`}tZM5X@9P?9s;TU6tbvU6_J;d+Ynpip=#xUxI67mR z=L^KmQInXZu)2aCUm%rDB@>{(iFlwUxO1#`7)k{u(NJ&->()LE8hWtN9b{Yw2qpVH zE`?3yUCX7sHn)Z*F@tGSXYv*nifhHFE2zGIuXhLslJ@)c9azFBo`@hy2D_STASs(U zHe3mp215)g+lWV}2G@aF#TBYmk_x`+15n|lGTDg7()ndW{cvYDhThzAx3962qTnmg zElt(bK@|S(7Z2KT_da)1%oft5)M(V1g#x-<4wDbmNHIA=p~z^)LQYuiy}FJbD!I3* zc5nbSR^5n1)wB{ZwNTU7-9@ecf)%#O>DI(xsyaX#{gn0yP)!`n$Ax@ zsvYe^;mnhjEs%lL+}POORtwpiJuPjc!;oh$le$w`9cb9pk(p4yFNN?dE<^5w9Eeb$ zv#zD(i%w8at*=A(52A;f zYMPt++k$?hGagx9v51wBXW>?we32L^pqvRM1erI~RNiZX5Ot9DKDcwYv976|G~U|U z)7R3|*F4hL)zaSF<1c6J;;agudTt9W(QF>8B@i*{7wvEh*ABn|Z>VdACiCjsfBfSo z_wKd#HB{DhcGYyWwM=&)CiT57Q}%4h#}kB|0lzEkOl!mz4E~_Q!W>`UkL>e!?Vuw7vCSEd-8xBPt0P zc6=T)8$UF{7t!Gv{owNlRW(gD70o}vIlOb1DeuDcOr?@kTxWA_ThG|U$nda;jBc#D zW2>+(`C_0KG;090piWCB?*0D5J73;`n$RyEz!h56(AwOKm~06lIDST98 zeRp@&XMg|qzy9^#e{lz@%>Mqjzx}&k|N4)=`^_Id?f%2@fRO`_R$}I|98+G4@QCB$?D(#;rE~Z=GVXdu#+rrVGz6P z+P=JBSz8IsR5dki1C3qvef36DkU(VyyrZgG?$^~#qKQLo91c0~)R$PlJb$I@8R%=Hu|UE<()!ii z&nQ$=s-vl|p2{HiVsl|ChD;}lhq|Xw_5Z=J?V z5^2$qe@gGFM&bmN$)--iSU&-eHmFfnBhNe&cN$P*qZ|&6;haBRa67q<^}Vy}H}9^# zdvW!B#aq<}|Ly5~LZWjy7e{UqF&gXY+ZyYJhvBhHvFl|!h5U*qu+RDQ!!J;5nqcX& zY`3N~qTGCryNZiGeRuW#N%mFn@wvg2649X3=B4cZ&9b4k7hx5MafEi}=D~g@y%wvk z6@5~LBn5X4WOCo`SSx;(Wl>`?FP?t;JRm4}gC1l4B6a%im!Hn1lrKJRBobL}x*Reu zhuqes7mo|#sdjGvh`@Zd7!rcpZEN0LICUH@8yLD^;90_cxezd~zkm0`>!=xxhLTNc zKgN+hnC`#x$&g>jlPmG{108Pe7*#5nW)AlB?f>}pS%FvI|K%SmI|mvOXjSNR71F0t z2fIUE4WE7dhY>1<9Xvnu4c50TxeWY!T}ULxtEaYB*NYRy1SiWAhO^PyFDmc-{?q2( z+REm+P%-A5+H|2E}xpqq2#%Z@F7rSH+i3yE4g5AWrAW&0)cPW<;dvD)Aq=_3xaAIOp9d_9MC`^YW$%jAfr}E73 z8{(H(lv5x?)2VI3!{amO!hu-@clqr`2u}U)AJ;U0TKCNAfEXdWL$8Gp1*O{HPA)91 zlrvcfe}YQ@Ozchkgl9@|zr!6( zh5SHALTUpX2^rWwAe8wMiJI#;_Bx18${w>V=75@nWwII1jCWVA&z#!BH$WQQ?)Q)UO4N@(YTBQPo zlPVfBXbZ`(&ld|9(!NN1}~FG$HKDZ zI+MYqR(MPR_2rXpmEP-&re;8s?RP2#P+Lx~6-cFe5ZPJ*x3TNhT0QKB&hGa)C58g% zjrmj43^D=56pJNdwMMLCOZhBWxR8&57F5ocNH|P|QUbbQybcBGm z-0X^jz}#u~LtY7}b)nXP2=&$D2nQ#9(5QL6GMz+W^Z7mCAFw)|dW}pjy^%q(4PFk0;nk()G6_>gr8dEJDq;go*DBQT#YPToN~)8~ zji3(>K-d>VcEG1ttgwU=$plh`N+B1hW5Clo00x*9|&-!^6c44$=hRbc7Z!{&ZhcJ4Pbq ziUbO|%$Z4NmUB)k(9ceX1+>0;wODB|7YfqYNmK*cgJ%RWK*2aM2!_z)vX^*H?;&NP|RzMOjc`SV^d38 zYfDEz_gbl?>VpeC}xP)l(3z!oB=H^T(VAX4Zg*V;YkYHK{luAfvz-Zpk z(A?5IjvAXljzBpOs8D-*24tHmx1hbdZxqRmLSY%Gl+9N7E0bvq9`H7pR00Nmj6KxY z&^g*t-%t-NlWk*Ys3Gm_?jD@z>+WXF$LOXW801=5kr0Ok#G=+_2PPX@(V$Mspc|>L zt*xuAtE+7SprF3GwXtb}Gz?4JmR2~H6&O3iOtiaK@!` zXyg);exkX#wxO}AvbF&#yDBST<7TJ?sjeMC_A}|j1D!-Bz8%zWP#B7uq;O0MsYK-v zqNYPZHwScqJsnVK*$6ACCRn03HPk}eI~0*Z*LKsu5XMeNlX^*fOkaCL1Dq8Mjsy#H zJ&ZbFd}?m3uW#(^L?arYO$82kTVG2!~vSo7e1b9-A;O%3c; z103{`!M@>vaRyH|O~Bwsk?1Kwh-Y&`?jQ&!0QfXYMRK+N=4qq=#XNqXzpcH#rxytu zwKvubwl#Ovb@#MEQ++2d);BUfL1u`R5$3;oLzHZ&wg56;06Jvgh?Rh(%UYUxd&c|6 zNOanblo(Awjt?ooj@W|hp%A+3YFdUY27yLmDM6@$#>gYm!G=5yYA1u;>N0EiN_JJ% z;0TDIv9+x|I3fa17$-`G`Z^jr+A$2oXdU#4(0DwS*<(=x8Y>ZU$uvmdgMc=k93H)D zDX;xbQ}ak~AC}(M@nE36o2=j}u-E8>@H{ zS2&^Ls*He-gOi6l4I!>1I+WOe+oZFxt>f;OpWUmz-!V$8xqqj=7g|*6nudlu8y_@v zwsdyX*4Iz#@pKYH2ZlYp&ZspDxl+*-l`91)OcKVTpOeeHIgoGn)C6k50uG;*2BWl#x(628V0^o@5lw6`|ad^}@jg zh^M)+uBx>WrmEWdI;c!;ZtEeAcXsvp-mW)c=*O6|1X*dk4^98qcP z9;GZJZ>(>ugYJU%)+YGV)NmJ?r>m=a2ixjfdi$DsIy;%YuxBL;?gMke|?0l&NR8BBPURd=8Psj|MdtEYXCglc8t$<4TFnZ#q3 zm`V$k&_O=7s4As_+@GNbpgs#6vu*#r^ z2o%iC}3tsDah1n8%~hWZ0_PUsT?QFZ%LM6>MAI($O(6PVNH7 z3B{L9@Tc*Zfu3P7+jNYu3GLO*5IJeG8!RB@5?cg9Ie+ZV7q>tB_`?sOaQpV{&!Gmq zafC=6>meZqCPqne3AJ~45TNevzP1KXf#bSc)gqfW5{hX-AHrivNX>wVeD(1kDn3;G z>YqP`uH`R3{^$>%e)a`4?cV>Q@W!<|n)hFa&lm0;lh^uv$-@X?2#ee~g{cY2|;8G4?p>H%+SXzu82>FRB4qj`xW z3)iMpO9iQwHEP}G_ddV-+3ipNc<1(~LtJVX4?nuyO;fbeiM@?oVCrsw zE{n#d=FXwko|eH8tK4^_gZRy74-xzg1cEry`q`ZthS4)z`|*dj@8AFO^U5j% zok^J8PR-$e78|4knN6xi*KEFAtv>fr#&zV#`&$vG)5_cj#18qPw-f z86trPu$dqYM`n=3BYo3oDypAj!s7AcZT%`Tjes}CH+4&It^&}uJ3)p++cerkfv&j$ zRCqJ2oa*jX`(*^x)-r3#BJmLz-3+(Mkvdy;8H@xJwr-k=7nyct`>W_y3I#zVBS>T& z8N*i&O_@mk&ErkeXcv-2A(91QvknrmBI~8ur;nbTKYn<)4J7elct$Qwt3pp%y>)1u zm^RhgJ&7715OF#kO#6IOCgHHaw9n&Ff!^9Kx$OTRd+!ct9 zW`$iz5YRha)76z(=`$iTy!VDK=)LzQNCrI_^q!0edWIpxFueEPXJkbB%t%{RIX!;_ z2%}!Dc4joA!{~y33Bvvqghf>XnFsH@_ej2b-?`_0M(hAF@x3RHH&50#4o~+__lG00 zE|YZ(N$x>Ae0BOAgE~b*jB{ zm`eO0w_Ar#9&YdM_+^7$uSY+$o!oly;@Ng5{pR&D3Yq8OXfQ2BEw1j-jol#yr&`#8 z5Ls46LL%Vj&5d{lww)^zX09&i+lq9rW_{h0m**F+`Yc%NgF7{h7MyKhiPw7fdTUrB zQEJGgZ8dH&ilvZq1VCb1e0sc_7L_#L`L>|0wF-e%dDmvN`d*M^YOgE3_1$lgG@K~7 zwGeDCsTgw^HWj_US@-`wPkjULO9HRzVQyWiZ%8(%d%`uj3=fK&!3FYE5g#d+>l=Vt!ew&QleiNdQAPEfOAV^cn+&pva+%6 z$S!qjX{C2=l~+Mi=6X@Z^^zwOxuMMPP!4F()1#U6$o%fpH=jPddAxUYur{3;n_SyD zKHcBknp#-fSe%?%-Efz~T0aJ>!AyK8Gd7mW4x~oLl9{o+XK#1+UcP#EvAe#ww0v-U zvH$o4{@>f%T%B83-H`orSnKx&!m+UoY$=X_sQqX()ju-4I<~PeHaM|=biDC&|8V~p zRDn*9jsP;TuspW_TKaSI<*>IO?TMrYVrkH%92!WEjwc4wnZ5HR07cHE23Akb&t6~b z&1_w~es;98y??Z_ygWZKH9avsO@h6Bj|+B9l7s1?Y-0TI@!kaJ z6#IQKPk-Og#=(=t@vWmL``gEFPC(3SdvSSka$+>Y|1a}v{r`0^1uRNCh))A`x7+UR zi;rf;hK82Tj;DNqo?ejA@wk17o!xBq@yX8K&dJfsi~WOx^{wT}>D*A7`_H~gB!WI) zBAbds?*$g1fWPQX!FMn)w>urv0jPIi5Y!c?N0%0JPmY%pW4o&dry#3zc(gG&3fSWq z16WZ5QxhX2nZfZ?FCZ7(u)YDegyG2K?(XJH5(GePCQV;F-j@e1^oIt*fyBb;`S$qI z;`qq)#BeIw-0rcXN{mF(*|A_`>f~Z;AiXkSjCq0F>yL!p;1iHr zJzP0`JeM388AuO}#etID>yIYG$(|5^hhzR=pvT?|VATF>EV{gvJ6t?FItiMh;XpFc z8y_0V&Yzx~Ek1d@KiQMY_C!+wpFt810tY1qJ&>3$5bf^|d!yr^TAl8h7)yo+W3hPH zs23{?YK_qy%Pt>Gp1+;id%n6j2}(D8JtZZ>+M|yP8smaH0F7_w3()|;&^E3Ssz}r%xAK?sWl}a~gipTrXsT|OH`+K34 z<@JT49vcWR+k1OLeph_$Bt5lsaQyUXu2W|pUmT4`Lw(U+3M0_x0=*$Ckl(%O{l%H} zw71XY2l#MLAOw(m6X>XUhQ{MVvom8m1L>)eg}DJhGWTVGz3UEwY&n-ik@y1L7K6uU z%}lS(9B=h_VMiFAKfp?O>;?}=b_PQ6zR8KDIXf3LA7S+?)5v536Cibh!6`hEL}I$b zeHx?L()av)Gc`K7Fh7%x23%f`1=PB2u0Bu?9Rc^VsGLIO)7e5$s!+@M?tX`{H{Qd> z5pZk;B{&>0s+I0nuXn~q=TDy>9`rjrF2J0-Ek<_)ezjyM&Cx0;HGD zl|mEDp%Ms13Z;2^p(_!hVB|4XYE+}Jk8Yi<%qM-&o&$(kIOGcV`23zw(5^9n+%^GE zX3@0<72K;(#TBr5GO5II5gd1u*<6XZd(Nh|#JoWm1pX*A$(-RZv?IOUDh|^Kl@%LI zlqh(LSU^+g022aC=S~Aal|`!~T?rbF*nx4(O^RhMW49gX4S+odUOO}n++-}YgRvNZ z;9_tj5{5yfXhL#;Gcy3`E4;@2ns*dEn{u#_?KnboO~dQb1Fp!K0-X_eFw$og)3Ag# z6c&TR01meUharfR+)Ou#!6Smie=nT|(%+2e#`KuA1A$Slr2{feXdq@u&kT%;FdZ$Z zR_JUXkO%}Yl7WQ|y8x)68*^c1k*3dirD5F24%8beZP{JEIRsSx*>BX$jVsS#-ep2GwXj%=cD zMBCol(k8+YfIJM+PaO@73^QA)Bw?V_DC|J9I5?I_;ZdkaY>&k~1BL=RqmnPA;st&F zWCkQSmJjS^@Qft3RkyI2A|B|u>n(f_6#=J1F0UHr44$B;7gS)PmwKv`xfp_ywBrG%_B9T>wN3JLFs;Ps&&JhEjGF zP)9p(Qr(CVz&K;MNVmH$IqEQVX-p;O`#l0UwE|+3aam#QQ=@ZBNiW}( zw%L8D{)EaM8yp$#Q9(g_ty+6cT#3RqpxQ}bJHXa6_&SqMNaz5u3xkk`rBQ^8@nrAN zBV}v4F0m09}AC zOs~$RJTovZ13j*25*Q(Uv7yP3d%@M%)`k%P&jpW#sTYr}t*xm=BbZ?ONnmpbJQgKL zml}tm!4p~7$eCPY@eBYsqm!VO9I#0Trwt8sAdm2wA}xc6ZGx@!Mo{oW0P!6Z7SK$X zfxD$xx7>g?$^yaJL4S5V9+*o^&Wzd&uAXE%*HsRz2Z~HAQV8WB&j^ifp!b|~cAkR7BBBAH6;K(@98 zQzRwJG`no=;?S`)t`fGYL;-CVJjYN2`9tY}M7H1OcDOa&Nk&yk73dMv)KxdNQQ8q8 zWI^S~O=O`=+C6!+(;Kj9cuX;kE22u=dNP_cHUuQhXgZmUJF?DT)J>|cEPGU1T2fk7 zT2WCA7&xTZW8siYQlUodNc8DI*AGu7P`P9r0YTN%NIvU8Zee-}Sbi>tEhYv#R_Nvx zflOg#MR8>ntfi}J>OdT#xgObsrnM3YbcR|4Mj2q}O~7{2BnBte7zm^jfgYz#=A|Jp zTsFF)>{^NTPqL+rO(4ktE&4q6qpG~Rj#AkSR@{xPXhbWv15an*NGt@=GRfo(cBZ^O zTle_pGN-nzvAnLLvZAP|vYhGFQVb}#i^l5mifX#WjyCD(9h3&pT0kP;h19_i5$OmP zD&ia3^raT3K0L`{>N_|_kjv8Ysns>D!Q2qDrL4HTsTo-5jr}66z-$|fV>&P`fU2N2 zcVH}?79QDXQ4Ag1vRm)>o*CF2F4^Kq_XI_xhUPM}Ik|@;@US2OO4m-!>9sbCsh`hq z$?ToQcQm*1DZq`662uEaD-u-aJ|8_fo0;+3rhSl>bYKj(WfpNnr?!pG?k3#ARs3@=@|^!TlkU0+NY4faHaWC#IvDkyfP zd|hfbk#O|x)4Me%l2%^QOy(lk5=;N?S|m0yu)fKlqw6Y4`EF@w{@v>(3JSe7HSDaV zNyUntteMb?>RVY_c1+nlBV$9&wdmIJdO8o+PTHAU9vu*`W|(GKQ(b!nZ7{m~>BZ+C zzw9g3_H-}BVQpoK<~b-qLqVBp_2`{dNaAoQjb-)i4YedTs=je*?ahH!U)HQSO*Eh} z?U4cJ%hF2C{}c}R#aDl);PL0}~gHN-W_0t3SE=_x}!tKP);Ah*l4;T?M{Y{{cjYX=E|%r2gx#;Jkm` zrS1gHQmBuWU6SHI1{6{d=_OI{bh$JiFgaxK@)Lq}7J3lR(=J%2<+Re9m9dw1?uAlPUL1ir5NLtzPQ z=-sQulWU3jBU-CV9~73{yIc4OQP+HR(xP%`Y?j`;SJT!)$p>!SDyn|)u&VH06B5VD z2d;m2yA=8GK~Z5{Tg%lsZhm+3CJI&Yu(}d}o%tgk6n}pkppjJt$f`#V@_~l-+MD-_ zTM+OKgFnu1&9Z8#LD_}1mm zL1R{b|9xR~TSrTM=~aLan|Xj^F0O2DDKF0-(X6)+0l9Lowz#gk;wn&5geO(yd+60w zMfpI%gWJSbL`6+?QC)3QK2Uu<9ysw~56d8A@;8_}%q;1^FWo3~iq& z#6tio?$-5uAk6JPSse=NF_rM*%LiOq!}`f~CS|~rO0S+MrpmSS=F`QFUr9sN=a1-& z%pDz{ogU{RN$b^D*v!(#k4|UT_NHexC-O(o%Ltu;t)+>Xjf=-8`M_L^L&9n|_LsI! z&ldB6MYq+}O&OZldHUnwd_K_9l^Gf_M{|1@&-YUKfM;N1d}niWck6s>dMqE{>tjQy zy$`RRfJ4OeRSF|AvvGR*7(^5nSMq^M?$Yqa)Yip|mxqa&eBit0P`}z0&F-C!`91l- z{aOl1-5u;3T6VfeAn+eBgqb&f9>U<|_doqSgn1GHjreCj4`CWZDB?5j{5*t#5Xq8) zf!4qLc?gR^kc`3-@#qDA9KwGslL|R>3W+D?asSoT5C&Q{kIewGs6@sq%-^I~B+3i8 zfk+WebTwj?I&kb_bAXcyf?xR~h@e->$jd4bKwbDMKnJccut;ISgufbH^+*ssB2lQo zO2y_!*Z)yn0~YjH(A*;bpEyiDrLqck@mf(hJWLhne4wJaCzQdeIF`(kA!jYLw@)vdSJ*OZo3mlVNC zTH5kQl$O_mxJ`9QX(OWj>Ku3OmDZP3G?W&#qVQJ_;GNqKDmuzbD@&V@?N{fx3vgN# zx~?3yY@4pm@u&=RV_Vzn%TRR{e;i%N#=8&8+tJlkkdwa(8dH1kVR;pzrK-C9Y78S9 zsvg~|DX&8}*EU?8gM_Ltx?73BVcJ@nuLd=ZQGfStaU&Aj-duAv+$4B#q<&CTS=ZM7 z$5Gy!Ls0C$Jl2(nDO^C_<$S6f+gHB;Di zn%T{{fv6Vdi>m-l>6kh`*jw>+GB8(nD(#(ITH0D#&x9goX8s)Q9A)3?+Qjtg$k@VY zK0vP`$UHOCnGul4-N*-KA{-JkkmpUmnR1x4-7=`jU~=gKcn7UAP*vzqm~^MRszI#FQ)fsUNjk%qv3z!;9T{5*#Hd!hwDkKxoX z*xCK-pT{t$dV-uO=I1dCOX7G2%m%OjJcdExArkIO_&seukKrD$^6ZUyBVfMz=T~Dm z3=(<3#tAxm6M?IzDGW$=A9NFg9$P#NH~F{m{;$#kSl*>Kn?dWb+oG}L145nxY)_3Eoe3Zf5crz&2QFU<=!h3%{!RVYR0b#S`9>lM6+%}&fWk1uBs%mkrK&D(K7arcnSnr|(fA6! z^^XB0Lf0jr@VT8lX+A)pqtRrUoK4nns8_R|M8gLsW~H7+2MAUE2#uD71Vci#jKP*( zT}35i5YSvQOCx6U<@qDJ6(HfwmK&8an1}NLe~5uV6ZuM=Oh^&s1A$H_7lR^_1@cbr z)nqFO^y^rtMxvN1;av?CAs%lubBGLP7nhfxCJ~P@81p$SY%h_P;~3<4DI2EiPhcXS3?W= zJN-vc-WL4-=Fi=K&|LfKKYphF&;Oo({GBwv+We~U>KFTe`Wq;md390_lgot8Kgg`o zDLfE+7Qns&4ap~x2=dMzpOBBuT`tUTrLdwu`|9o8E)fefN>Ml*kxjr6yKD|21zbxA zSTuCxXkCnvZU#}ce(mz_&KETaI6eqE{U8JjS|d~nffvYzKN)xSD#a8eo(0wt61G*o_2xw{7TJs)e0KSlmzSfj&i{+AF8)~n zraq8jp%94n-O)s^j!h+?sBrV7FtCLq%RX97O)YKy-G@(?&l5Y4@fz7JWP?>Hd;tH; zwdH;Vg+zru7YeAPd@=)D-Oy6ofNmizZNaB_Y+B4;!>tCvT#(=(^1y6l+BrRd=p`c=bM;^gsWnU!6bDcY;VSi!PSBr`K2cSTwT(i=g9yeuNgc zHKG8{%A!y>By$$jm8O%%V@NS;JE0@aP|po-Zp?GBI68#@6g@-*qPD%JsR2=5j-hqP z_zE18E)?~1Iew6v`zL_f0Wo0b?CRl^i-{v~rDPNd+g@9VsHmoL+uMpNktp!9rPMc| zxKw7d*bO<=Dw&Ya?OGa}8tHX1@DQct_v$EZttF2hk+=xB}oaPc%8HPILyVC2)m)3Ms1$yZwewQ(07TUGL)h|0&cBj}!*2W2Hl5+pULz&lqr z=IfHPhy<*bC&J5E9c|c_qUy5Jx<@rALQCO;B8fw)hN0DKm;L6T7_fLEfciz1|e zyrxvap-@21nSiJSrN)P4Rj{wsc(+7g%w&{fT3k!%?V=V?-TSY*L_(k_I^}E*n<3+| z^%5!piEX6-m#PRrq%EM#-1@MIV@+7i3Pv5N_V&GpkW~u`K3Z}^dNQxn?oFM)Yh5l{3v@V1)sSTYS6B6L&?e(isU2uCn@Y#DwBtDz+kvC1 z{NBC0ML?;mYGBF@Y=e=lz`|Bk`G5A+>7Quj>aKykm`BK>G0-T0?Sc3&TD-C{V8h`N z1$S@VdDvW7RDsiE943X_Kt`gO1V}5$quPf-|4v5Lvf7adMH^D9qQ3#W0(kfxLCx*f0?We`veoha~-N$$+kl$pN%jz(0?!-5h5z~Jsa zC~lB|#tgB!mQVt+(~xFR>8w_f$|k0WEIdNYiIebj;;5jis;siM6o|VwtB4OC+%Cj+ z#gwG`rFGC@Xet3$f&WUYH-t?9_SAv33W2SsK<|0*B2`t#B0sqQ;MSvu<@8KTc_GK; z^s{U3loeMulZfSzSEchIR*Sg|uEKz&D!h^2l#y&7IC-~0xZg3vLDV&>IXb!`8ZqPU z-K*{MBI*l%Us56jD7>M{31BD<{)Qq|& z^UBlXrG&Wo4kVV2_u2#;0UPFHf@@}WesNJdyyRu#8_EHNSa=(as>0(Rk=km?o9~xZ z`_HEj`x$NbZbBk67nZ@k92+*)@HElTcmj`8bTv0t78U_F4h9XQ7GG9!ulDYvNA)6{ z$Z9>%tDC<277_vf$tRKdCI%6MQTJ{q@=is-$t}Ns|IVEUu=(e1#}_@kS#Z0Q5-Dq@ zafTL@Zv3}@4vAx+ltdwP>UAU>%D%Enta${6B@b=`$h{a@Me`F4dLO&=PTAcmX=4c| zJ=$ke_1wMw+h2Zl^pA&=a+CG9b90&71jx#}MR)EKrCwu>sXh zD^gA!fY^!h;l1B{2bm=!L8E{RW4)X1oR|?;)|B2YYob%Ek;Sul-R6waHkfsYqIH#S zA32`Hi6T^H@i*k}Za^kF3lh&FfkwrmMjYb0`r4NIYF%t+KN*>yO3cRB6FBdXpQd-7 zY-C5354+6etvA2QMQd;6!?R`)0EQz2D&ZN_Y7 zn+L9~{%@MrM~~W>CBOUgzqnpr2G_JZ#6r*;r+3iJLNXnkHo6g={iB;(2a|h?;|VES z&*zjjc-IrFw)WEd?b!Ot>%ac)w|DY)Yg9lFg@_@7(iXcN@1O_zv{vW%{?z{5)5|BP zOD?{3FrZ=tNFjQC!R?0?)dhFHeenHn?m?zb#Anhl;P6HeVelTMBdSjdxl_kSvmjFW z?)lK%^yt}4d;nc2N7voG{-~&|;Kr|i^B3QI3;LeF(sS7qG8)-j+XnmdI%6>A(ox0k z&AF|K(X-{vz|PFjYzn^c+qb^EbN$!A$}G6|+kf-zZ+`=+_w;NUyns=4Ev-ao4Qau7 z-OcVu9qcd7_s+WcgL|jLeB7PVqJn}Ozxnm;+c$1}f8+Z1-`wUyYN?7zVsx;uDDaU& zBZCpI%aOJreDgc=%WLd~PBxQRQu*-qci$KO?z?Yp+`51J=C|M9_&2{P&_imIm`wuF zv=#(-IH6fQoyXBd(8POY_O_RE>^W1@!@StSt?$117W4`pKDhDV`+{5Fes}#o3R2k; zE-U|zXh$Kb0;uqYn<%3Cq-@?V|c?>GoYGv}R zkgDa-sh~Yi1--NmJeH$Lu$dmAG(5PNQPfnBYiW24rCl~XmstV73#*YUlbbwFi#9k6 zsYYm&VF@^%fJdTJP*|MK9MpR3KF6}vj;&!6_=H-zJpo8 zrveh0!;nY-i9_!|HX_<8nT7_x)DRxo#}yV-m6B;N5pNu93@+|vbfIo0qOQ2SpuR8s z$Fdko-~k)doE#bnpeGCxg~V~|?R;EE2OZ5O5ebs6P>?UcXD;WbH~R@fC~3=~x?_Ok zI-poM1UymW@CcxHi6i2%3=*EMr%zZ}Wc6CUq}?(k;rKzP5?Y{8_JS_}k?TnZBxKN| z>tJ^9br!gg(!VMudrsl_4;vakRJ1oEI*{@Qm@phq)dt5yLRJSxhQX6b(Xl>DZ{l>s z4$$J2%g>juR*$zKVZbSeF94h&@D$BNpS?%Mr=oBysL7=alX&&%^AHZz2I$$#-(Nl% z**}Hy^j{=&;Nu21hj@Z*dpH`>LV=E^;sH?;0#U`#f{)VJ&_o$M`ux-7)5O{_l)wbH z9QFahVUXZ_zOT$Lj&GDsAo&vTF?z*(RdQq_{tI&hhYKn zhDvBbf~ORs6;o5+$|6x2WPKV8mvSlnHl&2QH9|fIMBfHhR%W?491WgM0tQ`+Y(N5V z9RT+oWUN#m2R#nHD8c6Xpfvq6w^j=NaNXl`>k}?Ij>MBtFk~D8fE=~;6b=$uS&Kr0 zeFUw!8N+9=TljX!X}18%f~T4r9Uti>6LB=Pk&0&$kgc_imDNfi4qH*x+95>~s_W}2 zFikZr^a$j54H608vAt|hchkTPu9GI_gNs93eO*;OnkwWWt7?iHD2R68c#(O{)o8sJ z3e*3=-3gw_(sgn^{_!g5fV@sy1%}-vK*Z+fw5+( zgsU6%cgYw4sL*jl1PK$1z_wL2R97}tHeiT|;zwm-hop(ju1XNvJb!ACa9TCLq3#+)Mfg#nfhS*+pzqAd?(tl}D$P5OtPsxPm zU(RD{#XxMrA<0ekZROAzZ-ZYOQPRS&M9oGetC7+OIyJxg>L~)Cg$BOguVC=N8iOX+ za;XS3p%vQ_?5u!pdJ~ygQ&!VP(JJ+JKnh%|{d;P|ufF=xqm&ty!yvWCBU7<3cfyjZ z9f{|fOl$Vq>T3`Gmx{`gM|I7BG+=fCo(tdFhONn$z*ewKNp8gaLNG9Y;bJKPq@s)>=|tg;VxBO)`m&3OuoV30z_Xm)NY`omP>%n1GCvBhh*_wQqSd z0#+f##Y{>~J+ic@3M=YyfqxXP4MAa3AkD8eJKbiJ#VjJ5x==JZH?}-Qm-Yvv0JCc> zEh;BguhwMs8~$R)QTWq`3{D}=>KSOj%cU1Qaw!lL`t zl;Wbo61+O1B0s8VYOJnrt%B0>zqQ-!eP%9Gpp#K>1h$$4uAAxo{`w|1tF-7*L3wEv zZ3IzW#Bw_QoVt5e<@IeOYBl6FboV%-0wtdcrlTkf5z<;2gX=o|;x_8hqn4WL(kc$J zL)4QT_sMFD%BwmFZkq}Vc}^&K!O@3B<;no4sq{o*DKpucUO!kP72^g4$i|i~fu1Su z?XePy3LAR7+2!tb3gmevdW?Kvj!;BYi(+_YZD2H)@npAFM)~a|9=WKf*3%bYlRd#P zJE<1!*xuO<6`sCQ$%)r2SE{&-w)tIOiY-Dnp34lSW z0`{ma3%dvNF+ux%NUX?3U1GL`0XDltM{a6rW==CW>tPUEYJeqJT!6vU5{RV)RDEUZ zqpCXRUT&wKjwrkfiM;8)P7a3&L@*+OB1%ofVa8Oqw}7+@eE*<2!fhZ_l@&G=mX&Acj)z{;v9wCV$v@!`DSY_{KvKcHxswzrq9u*Z9JnSIi+-P!n$(`Z{ zmE>r73!OVKr*e^ReFs;Z9tL+?a;M1*Mws@s6;e|L0=9=9R##P*qiZNVz;O%ms~%Jp z)^@g5u;ST>UC~o;Ghg8Say+GGQ`i!njV-WmEeYC6izSh$H#+SC|ed>~j*Ka{)c`n>76?B>04yJo-LJB^mKvHGW?R~R{i~98irL}*+E{QeO zI0EFJB%U9ka>}mL@7(?6SAUNiy=FO+3Uw+LIpPz;M{IAYH$-+eQ@s;2@u}XG1lFAm z(M+zr^~{i_L}9INzyICM`y{w#BI&Rz#ZUsHRS5==oC7ZViC|8I;GNqQS?u)ZR}4TEzhK+OcR$~ z+3a3Vu3C|mk5I&xx;wwSd9xs2$U5~hK9JC1H;>6d65KRjpT=w*U7OsPIe2osz2f4V z`@>x{55>o9Dk>V|Gs=WnCA}Ms{P{gIBN*znH>m%8N zxn=+Q#6WJSz6=Omx9{Ej?#A64MTOsAhiVp53%vP08X4UJvl@v&F!(`*iib_DZq3d1 zPPn zJH5FuyUw0b!9t*{whR_y#kX!j1zhyt-mN>guU{`Tz*SM8ZbT=Mk${&&ba1%pK(~R| z74Ml?-&)ME#!amyc@puxTQ_bNLnGwTy^=dc_io<0Re*uiywwaHhe04PXsSr05n4

4_^7lVQeAQ`lL%V@SQ=A6ql)wf z4Owge!gudxtVtHOvc=Vv_ilgx=+6D>x?4qs70AlE@{(%uRS9gCGAI}%5`$%MiSQWf z6$)4%>3VZ(OCu#j9@C0Nls&rn{oO)lD-~H=LusoeplKq4$Oc!ni1PxwBn(V>1T05F zF*1}Ko!c1dU$kIQ%4QmmA}lJb7rC4kzgdXagHM#eX=91HA=N5jF@elV1`%8E!{K!$ zxD2mA6ds(-sOu}~4FDaaA!RdjL(9=HC?q3 zFqq7O28rE?tLNc)qGT zEXBaEE}@UR+|EQ~aU(s@Ie+=`)tg;G970x;{zZ5hD%#4Yd+YPF6B!UN5A=;6KHp75^rn$cZ@|9t^l)x-`|ZKFTc}RcPuE2Eg`xh6N@0XvygA&;GWP53D zayT6g%)Ec|X>Zu?2swk@26dOY&l5DTK=N3uQ|O+*girBh?D9vr#`)3u%-9_GH>QtX zyuHYT9dWNd=+YUxyG}x88R(GnUH*9A+^ZL#e^@-4{i}ca)hq1r-rC~q#6Wg@?Zx|x zq}!6RI&@)Ar%|bn%cXJ}S3F=1gu5rsu08&{!-?UKPy(MGZ!Ip)4iCS4{L`l=F|Rij z@c=be&ofBWJn+_Ja&-Q#fmBu%?smJ^J+3JzP5-;cC!0%?Q(JG|zB^w|xMS&|fC0=0 z<$N7q6pky^6uwFowm3apc4t6g=Y*CZM+0}ZFu8d7{MqZBV4u%Fyy$nN-Qa>HVKZ_O zgMr4Db@g{zK|i0sr{g*H1IRf(+F6;|{qbV!Vy!Rf>K~5`#~n^pr%=oksQsyECxGu6 zPN|fPMJZftym)dI3e%hCJDW>OuYNc0S-3 zt+hi8Ng!;!vaxYImzhY#N3+AZf!>hSsBr3q;5bKSv21pY6hlhS9zO5ANb2~6CY%sT z;J-N8+1y*5Kb;+#=m$zgbbh4IrE|yw626QEt4CSN;a8D_!MW4^nY5ejU;(5Qvh@4w z>)SK?`^yAc^pZEBh!q-uU99t{o^*3GG>G{#?kuW#*4iZ@CI5M z1d?$MG@&w{?N6U4L4=+_N!WQT6%k7}qz_kOBQB*>>W=v#?dag}WOrqAdwC+UJYnk{ zNFKg^X-u9?tbh!!2vp`Zz;(f6Y5e5Y?1)*!apAjEYDhcVKYDz)u(my!$gK>9HfGEN z;mP#2kHKVdgj_P7f@E~zfECe|+aGix=pqnu7Zaeg{O01|^x5|0#Ms7o(%&;M?@`HQ z`yaO%Oplj@r{Y-@hU<*SLHf3K)?%z?Dv>ERTJl|4`Ptd&(}ks}{*C2Xy(Jvzl*>F9 z9|x1qpGgT=s8cAckWk^DKD%6w(+RN5?o2HWs3C9f)!Uur_4&!6?9`eoJTiT-e>$L_ zKKcAtPqjqlxkn`uk3`3f(X+j&E=&h&Vt(_*h#&xYPd>q~wy~TYnjF{|dHvztlXq_) z&%gZg!(l+KODJ(9#^TOiP``fqYO|ZJT6pv2%l7IZ0?NaG@&4nBwWZC~Xm2)k`t0|g z-&}t9@apNu^Cbm?l+0OaapBp8MX7LfkNJ7L(UUJo0zlM|6%uwjWm zd9!PJ|w#^Iqie#mCQ|mvilix+cgZe>yteT-iQ2-tFIe z_97)#im5zVNI!V|_VW4E=jZ9I!_)cH7N0&gr5&1*8>UniY;9LvE0n;7%WFsfZgmj^ z>yu}@xoe8IT~fis(dUm(_TPSdvYC6mCSQ5HZeL!0|LVzp02>~n@Y@=jkaoD{`K#mO zjg?st%^pAPiMfV?GWYz(#k-I1-oM#6-X7Y1ca&Rw z+931nU~^?|G&wku86Giu*8&G~)6j+7-qX3m{`lFCfA#V7?N7h|Y5m#TA~Y|(D4ChMig*Q z5Dlb`);fTTK`Qxdb$MxWG-7r*l3)ZocQn7g6<0q0;mgN&Ydc##>*sH#om?~vnq`RQ zT4YB%su|ges6)CTRdlqmxR6;K@(m3-1E!O+qxHGNHPOn)A1~itcwQ$%8a0{CK((VN ztu2jh?KrS2ZEtI;#pi9Qx{o*K=Ef#Gy?uI%BQ-dDvYoPxtUrDW5RrZ7%W)nN>@#tw z*7{}&5ejnz0YSnx!y7{cskyz?mHCk@I1<~2CkMx~E32V_!O4U3PoG|{+xk;hD}#$A zL5bYlK|zpcEo?j)1%?+`I;0-$tu9SwW4_qPDEMm)F09O1)8jkG$EQE8um=~QEMjnx zh$ccin#roip~)&bgN&hi%n?XE++JSH4rO}=hll%Chv20%=JNz6w%&gHVF#%X_AcsF zWHOLgkRE{x3@Snrwyi5NXzt1PV~?Pyn;gvnl0Fr)4}x1QZhbJ2ErUACCKDvoGV}m1O+n-P`F1n{MHT zG#azC|Hb9e`_sdP^~LaDcI9Awc6$9Oq@ID@*XUSgZhkH{Gz7A6vr9+0ovDq5*F&=w zaojPi;d$nse?Iv9<^7kv<;{iR^y1P)c5LQt{;GQ$YqRhbjg0q?`|Q>}R$ykIzndP* zOutvi=sNpo&&Ja?AI~S>e*E&&v(4uxgM29q$6S1uPdx$`?3vlQK_EDLogn!w@{e-X zV=EJn-^oDcm+3M-fBWI5kH>4zKR(GW4!dL`A{E0pIr?J>?90wLy>{^+4XN~#&>FSf zr$EqcDp6--L^pf;tFXJhtK06hq zi-Gv67}?neAHbB=)y1Wn;n6tRu}(i} z$nL`K>uhLvC4D#+?-^$ybDO!dH*+SD&ir{uL)Gmr#e-8jCr?(!)U4wVKYjSPKX>%` z>5Nqu+r0K!`-|UPo}AAPEN=1SZa2>idITABYHV(4@{KLv2piX=w9wXu&E57?|7>PB zDE2+x8C>$@rr%$_KHYPUK6~-}^4-D5r<<4AVS6j!{W2Os@50s#cb7ITRD{j`bC=-b zr|h zipsE=jhexCKI@qX7oL8ZQ&H*48C5i*4G+i1*FLO_E_qFHZLc@69_vnx$QWHDQj1W? z2CMJ5M9|;O4Qlb?iOa?M?#*uE(91!+NFwicN)9ez^*5GEZe0#0`-#;gLwKTBqYiq5 z+~-P((J!=jXU$PBhvb$yfX29cD#p9^Bu_bCVHdTf?MJZ1TF7PbM7!^T7Pb zokC0nNnFH%HF4j>m|0;ENJSj*oH22H(dmit%tEk#WnJBG3_BAmc21XJcbmcX$NbqD zpE$U(HF@yhQFV<`K(AIsrUv^BjxHuk&J+yCbT)><>Fg6Tl3D-k=%HzTdg;|WV*m(> zfeu9|y7^>5R_|S&ADb#|sHr7BC{fyHGJ3N{$>ve56yBJTPr!)eftX7idTw{;p4r}B zeExDGF-dsgK3-oezIk}=7B9b<9G`noTk)u-xL7baFg4q26pI*C6|ZM&UMe7|du;;} zeDq{3JA5!U|MtV{-U07`dE8{P*yFp8$K$N|X@SU$L^fAcKD=AgZw94BD-l)KJTjk~ zaqDCVmCdx6@N8Xns~?`8Ozdy&zkP2IbFKTEQ%gBhh0V~#hZajM`PRb++&;Ia zC)~`}lRef%&JyK_mX?--0C^Op1r^Pu$nzTS(}v6lIQyq{p*XP6rj6vTI(t? zxElp66_#uyxe*u&=;ITB=J#rpwva-}l&H3S+Nfg+CZDDKj~}+oVCRz_7(YpFO(!Qi z6;ghAW!=L&r3J38z)DU(H*>!1w`as)ffUe+JA3SEmO-cUgV*h|{n7Buv!CE49D-Ka z%*DylUP`F|yYjLo{GFQxH`{3bxxIztiVs0*>Ge(0skHuoJ^-etBUtN9n zYX2yn%RSrPeDUmPYkt`x)-iZJiJ%UEG!?&ffUBcq9Xd%fglh> z$>obdj%zWKwgwWLi`gw0KB)z`#^wZ_1PkU?22Y{gcaQXVh`H)GY zHNv*E@b+CfjnBlornM8Ndp}%;%Ic(+sjPx!i_4@U0u?9L^SZi+#ttvve9kOfp1rxe z{QP8P)Y`8Dl}(9ADs3w)R`vP#hQ)44f8xXCIHSexH95LCzAXHJN;;il0TGAr>B+|r zAC8}0K6`!n>e`E=VP_X#DMyKgJpIiFC~Y^U(7cpe&Fp=6yNzsW$I=A~dS`F1!r9r$ zWzyx|&cKV0A9f!9@Q*IFzo>t9{_?O7IssZm8-XUY-Y91(Zr{E?^lH&MfA~HcRrfLI z3>5VR1+sW-^biJBUjOgQ{ zT1AN|DZQT3E#b)+WFW)>f~zagqoRt(%%Y$>H$NJhA6S2~`1r%@-tpnM%dFDjFx6!k z6|MZ?ZwspM1$C)J7ruqSXEQ-S3b2)ilq;Hcn?#*LpE;gRc{7R0(ahrL;oQi<;6MtL zN2Ri+lKM7!LkUPPmfx)-(0XJYttc=v7V`yMCSMbcda^!`NyM@YjE-zYM&~AnLWbdu z?cqM3PHWe*$>xIc*7gqA*1iuAgyP$^y)r6~Ko+Zg-BPhkX7fZMS&;KL2yuLWA~n17 zdL-e}g{;wlh0fs6@N6oz2&!acTUq7ZZ|~gv{$5c+tzgQ;DtBtstnU=*xylbWoo-eK8BIijG+|?7MKc14t!TY{zw*1AO||_Vjml^?J9wbKap+ zm`8njaKIeuw^~gOu$8emeTnVa;pL>A);;PoGx2m3SgSWck&i@6g(y<}_1}H>+uz)- zM!JD_$Pp>DI;nj-rptK)F1^ieG7k*eIkYzD}IgheAUIUUV)&CLXo z$$+f}e$Ks{*Ky>^Dr|8)BxQ@Z90pe;S39j5t<&wbdmUKVO%!t_ND>aF%}oh=;c zwR5Ooe%IIvg!0x7I+A6AT}?3(N$~Y_X?wf5ZM5w>Rtso!*ayYaF(9)_Y()|$A1F7CrS)va7&tQ6 z6yORDYH@a(FW`59bc_S+BqeMv0oz{J(bj|kGmR!pQzITp!C`U8(dDo|0tH3y?C4@j zH$E0im<1Y%Os2K_^cHY9_xrqNjZK#a*C3DtzLM8Y*R_>{RxOq-rh*>YgkRSe8J|h_ z&yQ@J@9m9@j62mjYgeb%>hL&2Ao>d)rrkO*k%??;!JyP8bW_w>e!r}^k;wx(2@=_@ z4$jYS>~F1Z?M|&f+vx1;166&sPNz|u!@l0$aZnEKcKJs}!?Lz!TpI@0+#04=6oa)6 zk^&D@Tiefznba^`PMlzNbb6T>t=jHYpjkGxP~Dr=0aZLVsB9%QyZTJ?GZsN?U=Lmb zCtDJM*EY7Xm~{G*!_o2K?&X*=q;W^(1g2zey^Drn$fQynD*$>+YY9nvV*{1#j@evX zmXQDWYB2+^f#qF^WwL)^Ft#6;=ej8iqyAjKBhZCGv5s;+urB7 zyTAUb`k`OD>+HRgn3-e}*c{)mRbAaZ?eD(1W=$|FECv^%r(LA9mQG)RGq`#C z?rgH_WPGi{-Ep+ozM+%$PM^FOTiocD3cMO)=SfG76&Iau%~c4*&eo02nktXh z*VDFh{QBl}dvCWiKRDqF8cRl&PPc1R`90J7`xcd@piHc@t z@_0$b)bg;Y(5}<^XO9kVZZ1db>PicMgI~|?*-qWc^PSQDt)uC3XXUuBbMf`nVfFMz zW#jA1o43awUcCju%*EM@-uJ#HtItw)I{)JJ-rk!@y``dhe&f+~=dc%qG>@-tUOwHt z+MgIa-+8pr)VHuYumm!-)sJ7DzkYhS*3vh9{ItsKU!BM=KK!s>ZtK(J*X-^+Ib7=Q z9d944+*o^jefH{E)9lIS{SZ|V5* z-N911^~u|}pWi)RJ-&Xi)>z(m@b=mD&C~PMiS+}gx245Vb$EI>T{FD6zOnM9xvR0K zddq9>Iy|WFkiD$9T$$`DY`;7nTWefec=O@%{J44M2|)P02Tbzy7ZWqBIVOu^s?5=~ zvH!BQta8Nd@2u%~-t2gDb8za_b{}58IW0E_w=BMj!uIo(h2xJ~LxVlXH&0HwmKXQN zi{_ryYi!cINmproZHG6|F%+;^K(Yd>nuOG@@N|#l{7zJ zXp%@g{k2nl)NYFmu9o^3Z~XgwzmZXeT$2={%W`1ZTA8XbbH(2 z>hjX~M(6nIQOQ_Mck{?@lcPNN=#j<{ zKAU50w)YuCKj&^K{)KXx(02d`RPRI9eRF zq;X}ks;0cuR$#0%<p)9k_gQ1p{B_+Guy%art@hfx`W=8wy?^|q*|+%~ z*xBJZOcI+y&YuC;w*H{oooDnHbU)hg6ljb3YNkBe{^OmQ@kjHEZ{B?D8E%`Xod=zK z^?iqDGeee@Wp_bPDig8*$bvmq3kpw-TBT4tvo*Ka67c2tYAe?In>VkP-BpKuo3AgQ zEbkqhe0>cXMQV=r7q@1rnT=Her&MX=8ju96wWqDTyI1V2(l^x&F4hd%Tq|p9ZBAqD zoAF>zbIYp_H!t@mfrN9;V;Xw(cyV-hecqhsaXElBk3x+gikVfDgF^?MliJwM)B z->7w!o17k{)ro~6S*q-&xx>>(S1*URF4x9O&G{qi1MPvS*82JYu%2Ihe17@z=E>0X z-oeM4H|N*q>rWq7I7`PzN*#rzG6IHW$5Y4Wr+d2}?-cdCXw1=Ror9Z~6V}?5(ec{0 zfxWfyz2`R{N7k06UVq%&dH3<+^}Dm7&YChH55#AcS#ulQgqFk6!MUk-1v2YSodP%v zJv^H1v*ZshbxsWp?%cfp^!(M!g{NiPpN=LE9-pioE=`wK)ixG6Trl`xiP=o&FRea1 zJHGl_ERc4NRW}74RU_?1cBL;kSlKhMzV+hj_51fn8{3aAuO7X;d2w+u2Hda%PPfN_ z%=YL!c4ggM+5GAL>(_m3USZ9{DWAEv&*OB0P{vu7UsO9fv;Xeu)AYv8#haU(XOGvW zg2SawtHti{`J_Zr$v|g8@e;^64t=~?vWV*dC#*QHX}m1o;{&YiD!^NBKRkW+^XJ*? zo7XRI-k+U6p9B;Dk5le&*vpVud08-*P`W<5J$Cf--GKt^u=V*qgRip_fQ<_+218*p z*j>JTeRS~xgfjn){`B$rVOPJ`;`d5GC{qiinv1ftFuiYAD<2-b=<4zJ0MfS3Xzv-X zbXlAGHGntn_LsG;PPQF?ef9eF%?%)3HVtlg08?0K^cPzYWy2~sMn7C`5e5glryHvB zePEAPR@k&y=hRg`s|^&`y!9<@qtjP!A8lOConGzDHg~O5Npw<%p|r%zko4Q+;==w( zyN0g=J+3aG_Eaft0F~X?>eUsDm*=-POwUfVEDdZvUw!mu@$g`Os=mC?r&4ejO23Io zffATnBB!Utr4-&q2zoVW3o?8Asyc@10(O_Zy0T}er*^b&YIa6SlZp+iOP}>dG9(;?c?R^^VaQ08I{zY;2Ep z*ZckD0kgK6$^aKyKxYEZT?Pfs3_3Jsh1yye3={xLcv)3z=LjHx`W-63`tDlXKAY*O zD{igsXsOf#$h6v|Gmv>=Aa7x^(V2knh9NW*I1Ir4BiJ@j0^G4Yf%?|&mVQ7-(+&YB z^XO0q*x;5=)YbtKw;Y(X=O}qV&JGmUYzY#}L}Up$bpS3OsBi+;x0ayIU))t!QrXkj z)m`oAUz`mV1p(Gx=V_^{v>SDb9AL>M;0gFSQk7HA$xspC8Cg76iQO?<*q6%#EwnO)%PH6Mp@8z0j$+D+t@eBq z09bfkl^fmO!RpF@2S5XgJ16Sr=2q7`8!F02#_a`lWlpU^0(7`imA0&i!%&IUh*T)B z5|HOMxj+Ue3o2=N(Hdu5-As;7Sx3e zE`1@LE~QD)@+z-dAmNR8HF~qdVhK3%3oO?1Qn$CJ#1$y3s_B><=?hlmtCUiOPyh_< z0Z36|1X>E2gvC^{4f*9|KCK|XaI{Ztc6r=2_I#JO$OlZ< zeI4qC7tD7xAcU?P2#2@C@x6m>a9KnkKp% zs{IbP$>abs2w$MAthBrwKyzIAcD+%l5CicwkV*4p0y*fY$kD5nD%spdcgJAIP*=~| z;^suz+-QAENuJAU&C7QcxxIm^@?Xlz|JLdEloW#2N10fx%JW$zmdYF!gUwgvIrQK% zJnXLM9a>l)A729=zbB7I+S@@_pVtYR@IBrlVBikm6-9opU8M!Vj7(NomLuqCW)kUi zKJX$0ww5wrUcCvdqj$FU4j=AZ9y)4*HAQaV&$RGVfEd?UrRWDZx zMeTWP`Y+V~Tc*|Ks-@EZ3T9ru-&lLtvRh#qUh@>p4{dMv+Nzg_`^Gn}#?P+*b@8TI z<;a;|>b!W;UcEg3?&jwDUvC>57N_>#9Iww#uRPlS{&N2NoAKxKhtE2S8~eY1A1-m! zjSbg(+n-%r9{+jKn=4TSf-7sAYqd2URe#(CGyi^l^y+if#OsSs&kkN6b=*wYKYrN$ zc(t(J3^EoqTQ6seX8T)fx`K_pQ{K(Ty?l*wba`@qMVeRIz553^gPVW7z1m)WfB3Se zcJ{;Lxz`@^>BY_U`IGZ$LvE+Tw|uiZ-CpF~1^cQGFHX8-`j**4P*3c2wQtQ|-28R( z`NQ|KI{)Bi?NDCb+|jFxx%}YK(^qFj!1iv)vatWQscfOYuyFa)v-8{7>%X%eZ?#Sz zAMTd+)p`ngwy!~L=+ncYHsHM0yD@oo^?GFY`o-Sy)!T!iVDDbj+UcKPKXfncG=cQ$ z&HKx5|NI1knNQz7uYakUs_OG}zk2@XU(e4z9@uOXw^a{b}zd};0Q`praH=lNb`^J_r3c2DZOogf3dx6?JZ zIdRZ;`R3z~zyA57|H<{s>mMf@#~(Lep6u+N`ArA!_rCmmI?*)Ne|u1+LzMPK?JpSXyKYrYN_~Y#I?d!6xh3U$+ch8^h%&r}tK70D5 ze|~;<;zM)M==<0G(~?)!H|q=2wKH#@ES~lsKl$e;hs^ z(^r)Qw>!%pK6&+Nrh9a!rfqw2=JRyrH+NmPk+Aec6S}+w+vSIz1uy0@#XyS zqqX-R-hJ77clL3ueg8w7t=d#_(B0nAx6sx*x7p=!PR$JUu77Rs`C6T~w*7UZv7o5# zpnY?%XZB?M;?1}7H`hC3OFerJFFxl-@e9yS3T$MvVjJG)Q6y*fKDR2GfwJ>MP~UmAZH{M0(UxYe>UcQAA` zRpuFQUaV^ydHu4%(e=6LVXn?L`@{#pIv&@-2zGr#d9xH+@;XmWA$ zq1)HBHPu)-((`C*u4}lpt8Ha{Yv=vQSmWV-@9D{uSRve-Zl9Q*I{Ef=YiaE8@l%e# zt1ju`S8h*?oL-&J_D;6;_0+acJe;aoJvn)LvUa|;_2RN)e{g+n_r+*QckSh+r(%A4 zYWMY8=gQ^tqZ?6<-Z0)6R8Bqq{PJ{SWwf$lptNRhX<@mvXL(__vuN#Xes}3Z_oJsD zZ*H2`CXS{j%`J=TFaNr3RZcuUIeQ7}x5i4LvVZB#)#$|dP_VsZ##?+$vDvsXD*y)sXg6MX*c<<<67Uspv* zU&ZwEtLAoh=i1m=cmCx27b^!Z4j+HK`7wJid(gYzIoCD6@!{jiY4PqsRm+IaS7^2X z2iuF~+5MThLW@eXc6xC(H`=G|p6q&h`0&La8y!91GTeNyvx4GatX*{Csur z;mxzdio%7@&yLsMzB~1lx3<+4+KeS0f!w9+0xF*;GY{8?XAa%2>hX@Ik+Fu>lJ>TX zkpWa3_a~<3kDtsRef@Ivbfu=Xx_i**tPx3+f`aOv z(^q@rpWlAjKL7G|sLsE)Hw<`X=WDa$BNH2!e|)$&`}Y0Ki^cuTKc0c==BxMTr@akh zg{_@|;o%%EKd*$@dwO~E;nSz3t((2YR!{lP*~(n!qnYXD(UtkR^RGa+bn|s%=l!#9 zHy^Km{BiW*b6dl}>Cr^f*fkG{=kI|pCCojv{Z?cJY$d|ug_8XIjY&*64em#x+FCf{E?+1vQ&(H30xYs=b# zFW(+)dyDqouDv|je|_`k^?&^H-%qY8uCBkIzW#W#cXhVgJv=_!+1SHlPmOmrE49y_ ze*gLQ=huKtF>x?HJ6_klz1rq8wDfKdukXHma|1La-(Nkyc=wN=-+$iRe81jqZ|&%> ztZQl&OL{@v;2(L?dHC$&$6v#ALB;Tk^ES`Oc5^i-Pw7f~y{%&hCttq&yxx0q^BvS> z{_*woS?6|Vbx~m;*gH_9R*gM=)Y5c1Su;QP?PjAuIWXBX+Tt5I?g`X4S?%WDq1nlW zFW29$ufP3u^Vd&M-}&puL0?s6L!G9kv2Ii>FPU4(6STfOd$a!%B!%Q+x!Udz+N(EK zgX7IX&;wdAQ9b$j=gr0Ie}Q1;7tUXQeEqbvSX12Is4FP$S>|gx$M`(%^2d|*qt`!1 zXBwyM`Hnn$(c)2Gb>aA=!EJC=R{r(QrWxk*74lV2zXpAORQU6FWXXJ4a zgBMs276|*dW{*bNeU*mX%I4s}*Ehw}=Xc*f zA6$I?e%#kwR;$qqIF`O5sfdwds+5~o+dUx93@pnG005t74$k*19FDXWmN@$x=2j)|ReXfLUiF=bkp5p1h+EfRasQtwlUWafMuNQviL>}BKAqriN# zIp0*fyt#5QbnyD=@pSXSo0kXkBX!m7Z8l>UsAno|I?!P)7PE9bX^UO!P^;|T#+m|G zfwR72X6ATpaTXM;JtLD7dr$v)zB1T2*}6E^0h$;rs(e#E4eU#e=3I?}sSt@I{1&(0 zSd?GbG`?8vapYCj4Nojj>>ljQ7#C_AO7^#=+k<6YC;g+q{X(Tw$kjT&ge_BOt+`II z#KPygjh%r4e^aBQu%T(B%hS~Pu)n5pZhdL4**Cwv)>7UzKUuG@oapUyS&eFyP%IUK zZJkDDX>{ar%Oos1+ge*y;M}fq*lK3`n&-B=cPG4nfY?+C?B%=WPIgzX)_Y9NM|~9r zrBfvqaRfl|rP8)H7<~rbFRb!k{vVn^Z*6e+TsliZpuDvGa8eFclrmKfseO@!T{a&jYhTAArlG3e63}$%fRR66foJGvTC)lOuF3$no|uqxh4>d z0GWTDHP7ktlsIaZp6<--%-8xXGKoS4*uDZ8i^JlY8&?_~oea>tTEy{fj#pX>au0pL z>d9d>H|FWhx%vvX(Nf~H`70`egFq-+QEU>*_&ETb$0V`@penMuItaQQSuTBWcxz>@ z%04`Nv7$Bu1B%vC&~d7=dE6dfAvkHD-&0uHGtyn80h@6Vlf~w+M5=t5tG5EQ!Kh`f z?MJIiANG0+doJI-C^6Z}i(Mv-GS^%HLJMD@qySjYJ3PSpzEZCi%ViQaD9W$}GQC)) zQ7S6bQoif``OD9rKfT=?n>e|g4OBOl+4Jpr1`rMudhJ$^(^^>Q1YW{^H<0Ry#9Rhj z#3!>QQfDn_5w%F^YWKUV=O3;=eg637*~!DMf$q+RVgOIl>1|F6=<)=X$4-aSSpaxe zPK{gu{7^(H9+Syy8Y<2$67%#Xo9iDxPJyZ7$;H!~mrI>hj|VHt{7y%%!l+eROg1k- z2^H7?ohr|uR!e1Kx=g~63VB8e@CdP+0fn>3ar5Kx+3VBm%k!HbKR?X8Uu$hB(phy{ zjn71S4sOm!L-lPhtwbOk_Z`|IiY#jAIJzI*@Ym!JRq z`E9s;-aCz3p_rCr7 z$6r5x{^RG@uYbJo1j=je1$lsCrq$VO#Z~<#yW8feSz7Kd=5ysjiI~NkG%=_&3SgO) z6craZ|7S2${%^Zw&#%|}6i||>kefr(Ys>dq3v7kv&Z~C^U;Fzj?J%XTw6C((FU{?_ zx%n`%XRGL%^)M7dG7$p^E@?>G+{9|-UC zdth>*Ovau)x_G@hXA<@YjbdCPB`MM2l;S9G( zYu=pN^D#)r$n@5SvjJBic+&4fBteXoS{*wD8XcY%6BCsI8Gh8iy0Q9jbYx(~Smwnt zkR1H5(XnPP{x`!UzE-Jt(Rs2E|DWnNCgdFvA;*%i$F4Q2vS%|1|D<^GI5c~Dbt`byE(G?W5?$* z7#n;>;JzqeO_Z4W{ALB$Gh`v@(FyU0gov<|q>%d|4>CnWL`qm@auyem7Uj5v+{S)T z!ti*vpZTjmI;W(o$Ya#yFmGY<;`&mFPOd@FnTc^JsfgHw=*S1Zg=V1Ah>_t5i2^wl zulE|{+sy%+zIXD~+o`JNo%X7hiJ^+c<*{M_(-XRUd0Leq2Z_s$KYGzuSJ~HFB?k$p zJfp=-bwH@ui7}b+cnvP#W^NR&4ntR$~?Y1`MP~5KqfI&$auuP2lAMh z`za_$T2>ewLQe~iymvp+><9fsEld8q1tvmAM3z_vTnI#tR}<9nHNNMYiL>ae>L{u1^b%4a-c7c2+kJj5avj#u*llio|HJ%|q3E z7DBVJvD1{7x3G6UYq8Vq%S2cbIUE+2=yvl0c}Z}Z2_GI68V^ek8*iy~SV7v-SCEYo zQL@x<2{70P&`96tlg*LZ>DlQ@uAoQ1*J~n|+o6fkib6KXrqfWy=*Zv0qZw&o4x8T~ z1rN?3IUOUTW?3=dRYwzulof;Z^@9(0pIsPOY#hgZQkd)VDOhRgdNqt_er6Dbr%4>LO5JGm0v_PZH z;8CN)BB&@NQJClOf&kJf$%4XQP;M3^w_ zq!~+xbtM)YnkK}tW3$Y}$Oj=|A)(P>SumTgLcRe+L~=U3 zp*>g~^ovPUhOT+8*k5l}N_n}9Cu7f>0S}17gNi6Qr56U3Bs2)iyvDQ7c;QXCeE z0$@HwY8tenp|r`P7Lhm#egB|!w928=y0z}Xb~A)*m*SY~EIS|%0H5LFD|`70|Pw4p>?gUVp2V)z1!&zE|G#fY>>I68s@QBp#2 z(Hxk@f>mljr+hXRi-BdNrzb-o*p^zCj7=mHxut_t;6{MNp!1BzilVB)^C>Nb7X2WV z6A5E-*pUJ(s4HMuGB$^f11Me+8jVWNNJ>tZcr03A{fiaW6{DDmsB{_I%f+dPWJi9V zFf}wXBn%ge5lrmU!y}R*Sb;*wAQ7_&WFc?@1|73832A(VLd_*(F>I$jGA<$`0Z=sL z7zqbxibw#G8->K8+;od?wk<3=K8nGoGiU(iz@!LRh8!d+GbtLW0`?t3GJ#~T&JK^g zAD#%c*N>OzbGWGR=*Vazg)Yg-XIhrqIboq;57d0BfW_u>6ntf#jff^95)zqtiUK2t zL9$i4lai44A@`%wOuPFh&318GL}WNL(4Cc!jY`6(b2K zWD;DSYs}MWd5o4CdrU@rD(1n1c$nE*YpN`Ba3N{Y&8K(>B0*{PHZkLq&~&OmU?}l6 zmg!XjY$gJe3RMA;MlRqBQJbpJ4-#S$xGDGU-%CgnK@s_FuY*{Wx?h?U@gPQPdYt%VeU+q0FFgXLC z@E|`MRhfvy7y>IdB^^RZPtOto>IIcfpyrsgC}b8e9D-!fXo|esB}K8OND9r-0XGMCkqB9y|yQi$$d-M2Cj_{kLE5r{Gy4S)C!5i$r5!d?p}_ zvM2ygjmv_88z?58!Q^V>9EPZz7aIQH-mkwt2>t!R@8D^b9+w{Td+2X}zjrSYMoZ$> z+0;-NohW57z&%F9p-E&i1`5oNQqt&Dg+fH(=OZFQ!$ZPC@Bbe1`)?t?{~y1C>lYI8 z%YzqTQ89_J$&UQSyu>V#5Cm5YE*pzMp%Dlqpe_UZEEj$(5x<6pgx(7UC-K|; zU+y)8#QpvE2fyAA`R!f^f&oG3l}7N;74Ud$09pkL7m*DE4pGqb^b{POLC;Es#D#_Y z_TWBJPs@n^{lRa)KZu5g-@Er)*su2<+zU%2z)@*tkMD$ncP;xR5k>wjhhF z0*Xf(1&_i2ssIuOhi4*_Vj^Rb;aA1BN!EAN;e380hW zGb7`n8A%~Ypw`F%xL`~+nwkwm0_XrFEipNphDuG335Vl>l}l5+v|XS)-@j!m>zwwm0Zy2LqYz1O3}bm_H8@(%B=EK>EKOFY#&1z&r7&Bb zgJ9;})igPSrSI#XUa3@YXV0F$S)0of4h2n8QW`Biwa_i6FcE!odz-KO+$L^)3)N7n z7APCsI5@$0eslHW=FsNEp&VmVs~3m;?y~BG?xL*J3{!={$jv|`#6we(k`U0r{l1ko zKt1g4TQZdU2^@@cT!__9fBg9L`f4hWhvsc^2fUx7?xRj?Ap|Y@IO)%)roqzrL=HpS*=@Qj3yt+C1XcH7WJ=sWLMP|(9 zX34U2^mJHiQesSehX3X1qql3dCrY*iz#@<|4mPjid8J}}bLaKq>YLN8T0Ya-x_vn3 z7L$QFLaKlaPvbHmFen5K?i+k2q;6$(?Bc_`ak#Ox01ZPSrCDTtuoUgoABH{mA&^>Z2-#XrI|@AeNkI(8wzcE zvED8oYaSTkq6HR^%OS&)BGc2d)6?)Eih|IHGNY809ha7V(p^0=Gj#BDw|2%~q~~Bb zGH!s~Z5N$C3QU#eom#}CRGuUiipC(5BI2XsvN;4KH7*lI(hBGtNxU?~h? zDNWPZ-C0xa_N+cB4gz{oaZicIpjYs~+l?w|4CLrk`fL^(l9CR^B&8$+k^qMg)x`MlD5OhEN)x1uh^bi`rcl#bSXNtGS~B~t|LKdv;luIv zfrZ(LhjlK$O~|kqrk9OEfD2s;5Bt0;^=(5=j3EvL3U0Q=M@3hj=YJ@(I?&gB{V807nOvJj#ecl zMyBF3py+sHItv<;5E+wX@%yU$%~O8sGzVj#qRaC7yr^usUt41o>HJStXHT~5-s&s_ z1C|jR6GcmiN=SufI&#HClK`EVk^zA`oHfhiVz;T1(ziA>P%1p zJw7&;iA9q|Ku-YpJOw!f1n{Nhq4^|4W@TTYTOia|&g`tscT`sC&_peKp-Yvc5lb)< z1Pldb(Np3=qhd4DlCtRzug8*?!vza08wO?IF_=tfZhLL1N+xsl?LJ!DFUc!sQe?2X zJpipI5m;gpHyLfACq%?VMa9I&pb=J2rA{Ows2S)iI1Ejab24$Qm9=$RIk*bG_QhTQ z!T=MKm1a5|99s6v7*e`~l9IuQi%N|SkB&}AOt!kjY$KPO3&H_75{04Cps2d~ia?b| z#$a-^jngImIt!48Xl4$^&YH*i^7%Xj5uXZ)j7UnAQQ=TdtxB!razrvR;C|pR+1VhX zs;DWdbEzdXUXH$ZC~v6334$42LC?<7#PrIL5rK?lBxlFPDG@9QR436(l{Aq>LIs!* z;JccIOv{8bEBr;dw|xyc{s9M0%GayS#u|=4F#mM1r=|#<8J~rV<7VbE;s{CnEWM4a z)=AkEA^}7c@Ju)`CL%UhJLFs%oh}FrvZ!n+mOuc`-c|m}-qSfFm6;Ho!Hdu0aQX2f z0tQZ|aKO$L2tCO>I(YNLp=qgbDbR#)8AOV>K7eDUVxV%aPe4@BsZLwJ1Qr(?6GKYD zizW`3@o{OH6tP0Yq0&>Cl-e5SUo01nFNgiC<6#or# za3~}kmP*pF1yZ?GBIUGrGo!+zBC+HQu(ym13y(~vw|S`MEIEe+iHVOlu|x$-vV<>C zNTmXqO~u84n;;{d!RJd93h*M8ROcin#76!SQU168mq16N5@Vwxq7tBh!lY&c7pjcJ zglx5tru1?6LN=2r=gLHQG&(yg1400f(h@nJFJPL{@ubxBh_LWjENE_tjSi1bN+ISV zQIJFePn!YDq#@whK=;L9v1v?&K@0rPz=k?KgUQIzNx57W(+ZWL5~3qQLn9*6a5Pw2 zB={pT1Hxy)7&sOKj=`tXm_z}e!DKTjJZ%mZ2~49i($dIG3FvMSf_OHImIz6V4Sx_C z9TgdmLZn88hJ=JhB_zZ7DmNWLprTQDsF=qVNJMld09b((CoDZJl>!198IbakW$AGd zAf)>xto&~?;Stdw6`PtA1CH?^971ACa_Wp4Aqq!Cin(lI4iCT;=|nUDb!8@}(7C)^ z8IL8Z62-(thJ&Ca5=17U(dke~W@2Pa$b+!36a+Jk*I?Bm5G)E<+$<)CP9_2;H#|s6 zWTa)X7&$T_L*T%IB^nkL8U7$FBm~3m6sX0=Pid=8I60a3v%whRFfLz%y%m;demLZm~I6JnzxV=1OUdD&zjJrf4yAdqZU zYJAk~0b--$k!YH(N;of*%D61x<4p&F4LAZ64zm9#5J*~ba$H+zhOp{{SU_ze{m z>(`s@Z38uj|Ks1k{m0~Fe-qPHIB~wP*yk<+;_fBU5wb`4^9$ z_MgoFLzq`hCBWyZbhf92f!A!_cEJDhUq>9O-g|ud{m*Ha{_@*j|M;>|Y&%{8UXV;P zhcVPutT*vjo}51U*H$n;ccNF}9{{Sa{d&-*<@>J2`tsY?;|@B9C*v{I!eF6}0|0bn2981-|Muy_+l|YeO^dCdv7hB%cjsl376yI)k{TOfaoMqIVE3j-v8@l;k8|t&l3xU zTBES0|GL?}eRlqS@9fW~XI;A7=It;4xa@N(1v(quso)Uvtu&s14G_32HHR^FwRiCI z`;LESez=Lx74y7YnZBo%-BMVXZ#H$${WwuFc{x?nv*p0ZH&>7=WXM=dF^$frG8lHF zC|C0M_-O9&`Jep*&v%Y`48Xp zEE0~a$Ww?dM6iXZc519T3X{1V^bbE>d2;=rF`npHdxL@RBT)lc3IuFZ{&Pd=KQs;cp*wOW0S zb7#?A=LvAEb^uOgi%ASJ36CX;6uev#k->2JO!CI5%A!|my``?%gTKC>&#ZksUORmA za{2UXf3&Q1#5O$CR#8;o;Va!DS`Je{r{XE#Z6KEM&;Uej0SQEneI5k*dz&MN(^voc z@a60Cg^ASDBYo59jNvPoB?vjb?k3zq!ZILO00hcrIC4t01FDyk@_K zoKJPB>4Ivlz2srzz}$3S@A2oJH*eoOxxUz1efi|&*Nx%vE{nFa?ChY_29&#Iriw!4 zNv$>}9#1ZAl(8%LBC06gQz7N8jt|UlOb^WeYp|`|I&yvr^jKeS&L^Mlw<~2ix`#(c ztNkX|xNXF4B%rd5lBi4)6ZjF)%z~iHFYhQ*?M;&-3k^jx`&4j0 z*S-AXZJwxXf3k79sJf)?`2E(G?uG7Lqp4KPkP>iSA_dKra`_UHh-u`KNLVb@+uc3d zGyAx!WK+VgQ!=_-%T+Xvv#a!>-(K4M@zLd{$G-LfHcQXt6Nz{uK-scb+#0t|(Odw$ z6G0xXx?%e9@$p<=;YI;lA(95UPd=5&Ez0?wrJ}m&XWehVKfel$j0x~EG8WCiWvex< zE$(_(Ro5sDcL`7i+2gQg^46(g6aJgyM^U`{^s3^*L{3`eN-Adr4?1M3YW$LdTVfG|K+Q#p}pgi z5q-g&{mFEvcBG8QB)GdR!_`8qBuFIVa0C;N+*~!{%?su=5AbNRBCWVfXw_Ks`FXaE zouR?4r!W3^QKAQWoc6cFWj$RUGoM=))HKcLjYTvJjqXyJmGw7Nbw`?Sn)j2p@W0{+iZym^&h=-hF51%7p8_Wzmnm}Z5s0w|{ z@L-_Ql%vvefRw_h5P-snX>qE()ZYSonsYxjC z?juo23SnI*cst~C%hUoMnf?*sB6|}noZ__QUuOlEK%#|%>ME2_TD-ffk?M3JQ;1-s4Mm4d!Kwg z{r2$5X{FsR$W<{Zcr2Y&q!;oWb9o-0-r({86^s=0g$tPsM$=$$q(UPjb2Rz;i`Bd1 z9o02MMZx3uf1JO5dtS>G6O0rIo>VF@xtYND+u~K)I-DvQFnbd7I2&#pn7jZqhXYq? z{@|cb#Q@7)Ja{Bj8(lyo(^1yjU&;hn02JVkuZYwXC$oBlg&gYP(9W7m|QORj0PC`(%(n zW0Fn4nhY?$bIn?(skum`5K@R7lM1+M6Nsd`X&R0Jo|=rNh5hbwuT6|1639V?(d%zB z*FBw=;)xVet;!825g z82Z>Kh!~Zw$w#FE422176AFt*SN9%Qw#~Z*xj90LP_F?vO#ob9Tr9=%X)GlkLt)o7 zKCE5sY*Nx$xWx-LPeLo}Y@O0F*b1G#z}c`izA;=^YRh5slyt69EzSiheFpZk@HqBA>(h$oj18*2P`n$c>vdz-)+RFsOPJRXN4HV6c=93Wz2 zt+im#*%*{W%cYRB(I_N}p&YK)ws3)MBojx$cWCpwOllx#^1D5DM`y8xC*bm#OweKh zRw7l%$emPYlPGu$7OOV#B@`^Eq2R#gU0Y)Y%Y;iON&-erQHS1U(`)r!Q;|&|0^Jo% zvPi^|h(w@J!6+u+wM;JV7wlk;Q^+A=07;yYTLwI~Ng8{A&f|gXOHMxUmDB1KxkU|S zUFKE;f%rr3#q>8HdHfa%!16B9%hMqXAbKLy_^=7#L_(!V&3${6N2gFEdER z3Z^|z0~(%k^*~4>1Se0YQ3asG9q5WkBr_R?>;TWJ7gFVCpA7MXz`tVSW=@;IR0k^|07z+?%l6lhX53X_Gxqj7L7fzBgxLEZ(0!r>S^ zGf}_OQpM+46mEk7TmyBERHM~`w-bX&qw`pD8J{9>)9@r57Okk+N0)+vd$T$r6 zF(ew5LKzs}?jz^89H8OLWV6d|o!+R) zfd3<1x3a(+ULIT2LzJSB!u;~;Y_*YP|I5G&*vJj#&69Wh8uP7Wz z07@As5H@5X(PRx@LGQG8*14Q!y-@?of)daeBLdG5K2T$k0OJ({A{-npx2C0UXl;lL z3Q2ivk)*+mWTsOlO+_`bCjh3WuVDb4e8F!m>$3@Kf%fZ~vK<1fhdwg!Rd#-r*;= zaKh-MR8?+a`@^0$vE=J2b3MBG!;i8WamblDKbY@Mr~6m5fBivvXN@L`QzJv3la_?-X0?bB^AqYpw`H>((kCY zA&EFRPl&{4;fd)k8YC(Wla1iteM=-HB_1O}L*c0kHstmru+&s03(qAmCna$=oys zIXf)@N)=?^ami8Hf^-xN0ZRtDBG&CkP{~vdDDV-W2sr-kia{Z8H~|Baa41+B=5|IL zBonCMNYQZ_Ks|Ch5Xs0$PYf5r?k7i*V{Qkc;E0H%bV5el1CSlP9Y_Inu6R63EDB3u zB;F3B0-jEKVk89CImgvS0o;@&+}EHmwP1}*ae5Dwgb5Ecr@aBc_i@sWkv zNN7fQ1S#_FxA-Gcm7-__KO;OT0)IP057Hs9Sgewp@gVf>82sq_5B{D^p};AzA$MO} zNQ;3y_&u3SK*juacf|%Zkd)sZ#6!`DteDi>V|WaRwD1SXVNtOe@btTb;skU^3^FYt zDl;8>I{*XJ0y?O>W`-c5k+%aqSRRu@K*NzcO3rDgTE14*fIF)#>Hs3A&~6}JN^G0|95YBo#7$Rg5i2jU@64--I@ za9FhP?#@dDIUT%=fXIU9@-uH|WM#lOd=l7TDl>$4D~*cLFtgyPnHV@sAikXeQa^NF z1|0%Jk!An$(Zod{X&G1*0fy8j-OeBpsrVFZ8Ux0`)9%(YjSpwh@!-^$5{%+@1{Nno z;IgvO>1c}b?kR%Jpz}bgm5e|k$@<$FNN9EzkqCYw4h6r%Sf@Y{uyi~l5j+F4?(V!q zE&>LL7iUGMCs6MeRw5D=p9ZJGlcQ48Gj5LoO#<02GFB>%&E(vzXLw2q9+8$nC8Q>@ zdABoCZY!Y?@evWYWZvB^z=TDMp>fgCu`ww8-D8E49Ou?1B9U?N)I|F2N6O;Tm69Zs z5FVeNaQCb-L4XD&g`gC|qvD{qGlWSo(V?kyMm9Yu`tHv2F_WR;5gAkpJ~;w?J7Y)( zO$!fC2Jc5q(%nb=dK5e+GA%YP1^g#&XBfyrRz@P=ZY98Ack47u$`^uM92E&@f)?IwpgV35$xq z{Vo4>qp7(Z^Wf;}hgTmToqxamayn1gR5^X}{n_N^!R+k-lfsodDyC~IL>GZOdd3VcK$&`Sif!rK9*N?^6Afd{#>rtglvTm~=#)qb0{%qDGj-p=T-1M``lQdyqJ z>>IirFl+h>%6*zVmAS}z_mQ^Z5L{8j&=9W%`9gU-Q$ZNy=v1W=YwN_OK`Rf&^y}rxgc4yZ{o<#(0Qi#rhOmo{48X z(Muw zDGx$t?$IVf*Sl`3p}v{Tp%-&(vWJp+I)&fX2ej{?PMf!WZ?UMFa4+(~^*Se=E60CO z#E=rQIwZDxXUKQ00IGxCZr70bX^-hi09k(alQ2qXyu0fVu-d`%`364sQq+2Ehe25Y z(^A11+NK4xBzjyD!{7MmtK_}sn(`P*4k^)#kT@bXuemBw6J>EAwP)7F1i`}roflov z!nL%vG|7NE)hW>FyoO+Kt)B2?ZP>+RX_7)Ng9_hmVklpqlqgh{R^Ew~ewNJpahaSr zs2X)TYnC3E<>M^_(joE0QG c #0C1C24", +", c #191623", +"< c #1A1E36", +"1 c #221C27", +"2 c #0E2B33", +"3 c #1C2538", +"4 c #252625", +"5 c #2D2C2B", +"6 c #35322D", +"7 c #272837", +"8 c #2C2F31", +"9 c #353333", +"0 c #383737", +"q c #3D3C3A", +"w c #473828", +"e c #5B3A28", +"r c #423E3C", +"t c #3B4B25", +"y c #4D4D09", +"u c #6E6E1E", +"i c #44423D", +"p c #4C5030", +"a c #1E2442", +"s c #113749", +"d c #242A44", +"f c #262B46", +"g c #2C3245", +"h c #2D324B", +"j c #32354C", +"k c #373949", +"l c #2E3451", +"z c #323653", +"x c #353A53", +"c c #3A3D54", +"v c #363B5A", +"b c #3A3E5A", +"n c #3A3E62", +"m c #433D4C", +"M c #423F69", +"N c #35454D", +"B c #3C4354", +"V c #3D425C", +"C c #3D4363", +"Z c #3E4769", +"A c #32576B", +"S c #1C6162", +"D c #454442", +"F c #484847", +"G c #4D4C4A", +"H c #524B49", +"J c #545B44", +"K c #52524E", +"L c #42455C", +"P c #474958", +"I c #524D53", +"U c #4C5359", +"Y c #545452", +"T c #585757", +"R c #5D5C5A", +"E c #67554E", +"W c #625D5B", +"Q c #5D6255", +"! c #64615D", +"~ c #726C52", +"^ c #424662", +"/ c #454A63", +"( c #4A4D64", +") c #444668", +"_ c #454A6B", +"` c #4A4D6A", +"' c #544E67", +"] c #4D5164", +"[ c #4D516B", +"{ c #585967", +"} c #52546C", +"| c #484D72", +" . c #4D5275", +".. c #535573", +"X. c #555877", +"o. c #5B5C74", +"O. c #5B5C7B", +"+. c #635C67", +"@. c #665D74", +"#. c #57676B", +"$. c #5E6279", +"%. c #656362", +"&. c #6A6768", +"*. c #736C6A", +"=. c #79716D", +"-. c #696877", +";. c #63647C", +":. c #746B77", +">. c #6B747B", +",. c #7A7676", +"<. c #865E26", +"1. c #8B7671", +"2. c #857A79", +"3. c #A07772", +"4. c #53805D", +"5. c #A09B2B", +"6. c #C1BF2D", +"7. c #D0CF2C", +"8. c #AB9155", +"9. c #A4A250", +"0. c #8B827C", +"q. c #B79C66", +"w. c #AC8E72", +"e. c #BAA46F", +"r. c #C3AD7B", +"t. c #D5D44E", +"y. c #E8E651", +"u. c #DBD86A", +"i. c #EFED6F", +"p. c #337783", +"a. c #5B5C86", +"s. c #5A5C94", +"d. c #5C6389", +"f. c #636483", +"g. c #6B6A87", +"h. c #64648B", +"j. c #736D86", +"k. c #6E728F", +"l. c #777589", +"z. c #686897", +"x. c #726E98", +"c. c #787798", +"v. c #5D5EA2", +"b. c #6868A6", +"n. c #6F71AF", +"m. c #7978A6", +"M. c #6A6BB5", +"N. c #7778B7", +"B. c #737AC2", +"V. c #867989", +"C. c #967D87", +"Z. c #867C96", +"A. c #927C94", +"S. c #A27F86", +"D. c #837DA7", +"F. c #827EB3", +"G. c #807FC0", +"H. c #2DA5A6", +"J. c #7B8495", +"K. c #7A85AF", +"L. c #58A2A3", +"P. c #64C3BF", +"I. c #7C85C8", +"U. c #7E8ED0", +"Y. c #65BBC7", +"T. c #34CFCF", +"R. c #54D6D5", +"E. c #67DBDC", +"W. c #51ECED", +"Q. c #6FECEA", +"!. c #898686", +"~. c #97878B", +"^. c #888599", +"/. c #988798", +"(. c #919291", +"). c #989697", +"_. c #A78789", +"`. c #A68B97", +"'. c #B29091", +"]. c #B99399", +"[. c #B5A98E", +"{. c #8886A8", +"}. c #968AA7", +"|. c #9895A9", +" X c #8988B7", +".X c #948DB6", +"XX c #8D92B0", +"oX c #9896B7", +"OX c #A48CA6", +"+X c #A696A8", +"@X c #B699A9", +"#X c #A58EB3", +"$X c #A69AB6", +"%X c #B799B7", +"&X c #94AAB6", +"*X c #A8A5B9", +"=X c #B0ACB2", +"-X c #B5A8B9", +";X c #C49794", +":X c #C4AD88", +">X c #C8B589", +",X c #CCBB95", +" U U s > & X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X @ X X X X X X X X X X X X X % @ X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X UX ", +" UXX X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ > X @ X X X X X X X X X X o 7 X X X X X X X X X X X X , X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > P ).8X{ 8 > X X X X % X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ o . X X X X X X X X 7 B X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X @ X X X X X X X X X X X 7 X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X , > X X % X X X X X X X X X X 7 X X X X X X X X X X X X & B X X X X @ @ X X X X X @ X X X @ @ X X X X X X X X X X X X X X X X > k.HXSX).7 @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X . X X X . X X X X , X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X o X X X X X UX ", +" UXX X X X X X X X X X X X X % X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X 2 K ).1X=.7 & . X X X X X X X X X X X X X X X X X X X X X X X % % X X X X X X X X X X X X X X X X X X X X 3 ~ g X X X X X X X X X X X > X X X X X X X X @ o X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X . X X UX ", +" UXX X X X X X X X X @ X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X : X X , X X X X X X X X X X X X X X X X X X X X X X > X X % X X X X X X X X X X X X X X X X - X X X X X & 2 P P s & % X X X @ X X X X X X X X X X X X , X X X X X X X - - . . X X X X X X @ X X X X X X @ X X X X i 1.3 X X X X X X X X X X X X X X X X X X X X X @ s #.> X X X X X X X X @ > =.X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X j X X X X X X X X X X @ X X X X X X X o @ X X X , X X X X X X X X X X X X X X X X X X X X X > @ X X X X X X X X X X X X X X X X X X X X X X X X & > > , X X % X X X X X X X X X X @ @ X X X X X X X X X X X O - X X X X X X X X 7 X X X X X X X X X X X X & X X X X X X X X X X X X X X X X @ X X X X X X ).GX> X X X X X X X @ > X X X X X X & & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X @ X X X X X X X @ X X X X X X X X X X X X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X @ & X X X X X X X X X 1.X X X X X 3 A > X X X X X X X X X X X X X X : 6 X X X X X X X X X X , X X X X X X X X O X X X X X X X X X o X X X X X X X X X X X X X X X X X X X @ X X X X @ X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X & , @ @ X X X X X X X X X X X X 6 @ X X X X X X X X X , X X @ X X X X X X X X X % % X @ X X X X @ X X X X X X X X X X X X X X X X X X X X 9 X X X X X X >.C X X X X X X X X X X X X X - X X X X X X X & X X X X X X X X X X X X X X X X X X @ & X X X X X X X X X =.X X X X X 2 A & X X X X X X X X X X X X X X : 6 X X X X X X X X X X , X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X , X 2 @ X X X X = 2X2 X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X @ X X X X . X X X X X X X X X X X X X X X X X X X X X X 3 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X w X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % % X X 4 +.> X X X X X X X X X X X X @ X X & X X X X X X X X X X X X X X X X # X X X X . X X X # X X X X X X @ @ X X X X X > X X X X X X X X X X X X X X X X X X @ X X X X X X X X , X 3 @ @ X X X > kX2 X X X O X X X X X X X X X X X X X X X X @ X X X X X X X X X X @ X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X % X . X O & X X X X X X X X X X X X > X 7 X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X 7 X X X X X X X X X X X X 3 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X 7 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X % X X X X X X X X X X X X X X X . X X X X X X X X & X X @ X X X X X X H X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X o o % X . X O & X X X X X X X X X X X X > X 5 X X X X X X X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X % X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X # X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > o X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X 7 X X X X X X X X X X X X X X X X X X % X X X k X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X . X . X X X X X X X X X X X X X X X X # X X X X X X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X . X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 7 X X X X X X X X X X X X X X X X X X % X X X l X X X X X X X X X X X X X X X X X @ X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X @ @ @ X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X 0 3 X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X o X X X X X % +.k X X X X X X X X X X X X X X X X X X X X X X X X X X % , X e , X X X X X X X X X X X X X X X X X X X X 3 9 X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ @ X X X X X X X X X X X X X X X X X X X X X % o X X X X X X X X X X X X X X 0 3 X X X X X X X X X UX ", +" UXX X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % 7 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X o & & , X X X X > % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X , > & X X X X X X X X 4 X X X X X X X X 5 X @ % X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % 7 X X X X X X X X X X X X X X X X X X X X X X X X X X UX ", +" UXX X X @ 3.X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 7 2 X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X % X X X X X #.X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O - X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 3.X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X UX ", +" UX& X X X X X X X X X X X X X X X X X X X X X X X X X X X X 4 X X X X & X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X . X X X X X X X X X X , X X X X X X X X X X X X X X X X X X & @ X X X X X X X X X X X X X X . O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X @ 7 X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X O - X X X . X X X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X * X X X X X X X X X X X X X X X X X X X X X X X X X X X X : X X X X # X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X . X X X X UX ", +" UXX X X X X X X X X X X X X X X X @ X % X X X X @ X X X X X X X X X X X X X @ X X X X X @ X X X X X & & X X X X X X X X @ % X X X X X X X X X X X X X X X X X X % > & X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X O X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X @ X X X X X X X X X X X X X @ X X X X X X X X X X X & & X X X X X X X X @ % X X X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X 9 & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X . % X X X X X X X X X X X X X X X o & X X , @ > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X : X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 9 & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X . , X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X o g X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > 3 X X X X X X X X X X X @ X : @ X X X X X X X X X H X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 8 X X X X X UX ", +" UXX X X X X X X X X X X X X X > X X X X X @ X X X X X X X X X X X X X X @ X X X X X X X X X X X @ X X X X X X X X X X X % X X X X X =.~.=.i X X X X X X X X X 7 : X X 7 X X X X X X = X X X X X X X X X X X X X X X X X X X X X X @ X X X X X . X X X 6 @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X ] X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X > X X 2 X X X X X X X X X X , 7 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X o o X X X X X X X X X X X @ X X X X X X X X X X X % X X X X X =.~.=.D X X UX ", +" UXX X X X - X X X X X X O X X & 4 > X X X X X X X X X & X X X X X X X X X X X O 3 & X X X X X X X X X X X X X X X X X X X X X X X o X 3 E I X X X X X X X X X X X X X X X X X X X X e X X X @ X X X X X X X X X X X X @ X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X @ X X X X X X X X X @ X X X X @ * P X X X X X X X X X X X X X X X X X X X 4 9 X X X X X X @ & @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X @ X X X X X X X X X X X X X X @ X X X X X X X X X X X - X @ X X X X O @ X & 4 > X X X X X X X X X & X X X X X X X X X X X O 3 & X X X X X X X X X X X X X X X X X X X X X o @ X X 3 E I X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X 1X%.X X X X X X X X X X X X X X , X X X X X X o N N X X & & X X X X X X X X & X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ m X X X X X X X . X X X X X X X X X X X & X X X X X X X X X X X X X X X X 4 8 X X X X @ @ > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ @ X X X X X X X X X X X X X X X X X X X X X X X X B X O @ X X X X X X X X X X X X X > X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X 1X+.X X X X X X X X X X X X X X , X X X X X X o N B X & & X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & > X X X X X @ X X X X X X X X O X X @ X X X X @ X X X X 3 X X X X X X X % > X X X X . X X X X X X X e X X X X X X X X X X X X X X X X X X X X X X X X X X % % X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X > X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X @ X @ X X E X X X - X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 7 X X X > X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X > > X X X X X @ X X X X X X X X O X X @ X X X X @ X X X X 3 o X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X . X X X X @ X X X X X X X X X X X X X X X X X & X X . X X X X X @ #.X X @ X X X X X @ X X X X X X % O X X X X @ @ X X X X X 4 X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X % X X X : . X X X X X X X 7 X @ X X X X X X X X X X X X X X . X X X X X X X X X X X X @ X X O X X X X X X X X X . X X X X X % X X X X X X X X X X X H X X X X X X X X X X X X X X X o X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X @ X X X X X X X X X X X X X X X X X & X X X X X X @ X X X X X X X X X X X X X X @ #.# X UX ", +" UXX > X X X X X X X X X X X X X X X X X X X X X . , X X O X X X X X X X X X , X O X X X X 0 T !.).(.).=X=X+X).).).=X+X*.G . o X X # o % X @ X k X X X X X X X X X 7 % X X 4 X X X X X X X , X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X 4 X X X X X X X X X X X X X . X X X X X X X X X @ , X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X . X X X X X X X X X X X X X X . X X X X X % X X X X O 9 X X X X X & X X X X X > X X X X X X X X X X X X X X X X @ X X X X X > X X X X X X X X X X X X X X X X X X X X X . , X X O X X X X X X X X X , X O X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X % UX ", +" UXX X X X X X X X X X X & X X X X X X X X X w.H X X X X X X X X X X % X X @ @ - : X X = 9 *.2.!.!.0.0.!.0.,.,.0.0.!.(.).[.=X=X=X=X,.r X X X X X X X . X X > > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X % % X X X X X X . X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X 6 @ X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X w.H X X X X X X X X X X % X X X @ - - X X X X X X X X X X X X X X X @ . X X X X O X X X X X X X X , UX ", +" UXX X X X X X X X X X X K.N X X X X X X X > r @ X : X X X X X X X E & X X X X X o 0 R Y K %.%.W T T K K R ! ! ! ! ! *.,.=.(.(.+X2X1X=X+XQ = . X @ X X X X X X X X X X X X X X X @ X X X X , X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X o . X X X X X X X X X X X X X X X X X X X X X . X X X % X X X X X X > o X X X X X X X X X X X X X X X X X X X X X X X X X K.N o X X X X X X > r X X 4 X X X X X X X E & X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X UX ", +" UXX X X X X X X X X X > , N o X X X X X X X X X X X X X X X X X X X X X X X o q F i D G F q q i K &.! &.*.,.,.! ! K T K R ! =.0.(.).(.(.!.(.R 4 . . @ X X X X X X X X X X X X . X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - X X X - X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X U 2 X X X X X X X X X X X X X X X X X X X X X X X X & = N o X X X X X X X @ X X @ X X X X X X X X X X X X X X X X @ X O X X X X X X X X X X X X X X X X X X X X X 3 X X X X X UX ", +" UXX X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 9 i 9 9 q D D G Y W W =.,.=.2.&.&.&.%.! &.%.R ! W K G R *.0.!.2.!.,.,.R * . X X % % X X X X X @ X X X X X X X X X 1 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X { X X X X X X X X X X X X X # ^.>.X X X X s >.C & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X UX ", +" UX@ & X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X 4 Y 0 9 6 q q 0 9 q R %.%.*.+.G U H ! *.T T K %.,.&.*.=.&.T T *.&.&.,.,.0.,.!.F o O X @ X X X . w.X X X X X X X X X X @ X X X X X X X X X . X X X X X @ X @ X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X % : X X X X % X X X X X X X X X X X X X X X X X @ > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X J.J.X X # s J.LXA X X . X X X @ & X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X O UX ", +" UXX X X X X X X X 3 X X X X X X X X X X X X X X @ X X X X X X X X G r 8 9 9 9 9 9 0 q T T T W %.q q D q r 0 i D r &.T T T &.,.2.T T R %.%.&.=.,.2.(.).; @ X X X 3 X - X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X , X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X o % X X X X > & X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X @ 7 ] X X X X s 2 # X @ X X X > X X X X o X X X X 3 X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X & X X X X & X X X X X X X X X X X X X X X X X X X X X X 0 X X X UX ", +" UXX X X X X X X & P X X X X X X X X X X X X X X X X X X X X X X = G 8 9 6 0 9 9 9 q 0 H H T Y H F 9 6 0 9 9 9 0 q D K G D G =.,.(.,.&.E Y Y R Y ! ,.2.).=XD o . X =.0 X X - X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X ).3 X X X @ & & X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X f M .d.K.U.I.tXiXtXrXyXyXtXtXyXyXtXtXI.I.U.I.I.K.N.s.| n f X X X X X X X X X X X X X X X X X X X X X . X - @ X X X X X X r X X X X X X X X X X X & X X X O @ X X . X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X & & 4 D 9 0 9 9 9 9 9 0 0 i D K F F 9 q 0 9 9 9 9 9 0 q q D r F F H ,.,.2.*.T W T Y Y K Y %.,.).).D o X X : - O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X @ X X X X X 3 | g.I.yXtXyXiXiXsXsXsXsXsXsXsXiXyXrXG.yXiXiXyXiXyXiXyXyXyXyXyXrXU.U.rXyXrXK.I.z.s.n a @ X X X X X X X & > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X w.O X X X X X X X UX ", +" UXX X X X X X X X X X X > X X X X X X X X X X X X X X X X X 4 q 5 9 8 5 9 9 9 0 q 0 0 0 F 0 9 0 0 9 9 8 8 9 9 9 q D i i D D G Y ! ,.,.W R ! ,.,.R Y F K =.+X+XH . @ % X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X . % X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X % X X X X X X U ).& X > @ X X X X X X X X X X X X X X X a a.K.sXsXsXkXsXiXiXsXsXiXiXsXyXU.U.U.I.B.M.B.B.B.F.I.K.G.B.I.rXrXrXyXrXiXyXyXyXyXU.U.K.rXU.rXrXrXB.I.N.Z f X X X X X X X X X X X X X X X X X X X X X X X X X X X # % X X X X X X X . X X X X X X X . UX ", +" UXX X X X X X X X X X X % X X % X X X X X X X X X X X X X 4 k q 9 6 5 5 6 q q q q q q r 9 9 q q 0 0 0 0 9 0 q 0 q i q q D i G K *.=.!.=.&.&.%.%.! R Y K R ,.).(.F . . X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X % X X X X X X X 7 % X B # X X X X X X X X X X X h a.yXyXyXiXsXsXhXhXsXiXiXiXyXrXG.B.G.N.M.M.B.N.N.G.rXrXaXiXpXiXiXrXrXrXyXiX XG.rX XyXiXiXyXI.B.N.rXrXrXyXyXrXrXrXyXI.rXa.z X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - e @ @ @ X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X @ , X X # X X X X X X 4 q r i 5 8 8 6 0 0 q i q q 0 q q q q 0 9 6 9 8 9 9 0 q q i r r G G Y T R &.,.,.2.,.(.*.*.&.%.R R ,.2.!.!.q X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X . X X X X X . X X X . X X X X X X X X X X X > . X X . X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X @ X X X X O % X X X X X 3 s.rXsXsXsXsXiXiXhXzXiXiXuXrXK.rXN.F.G.F.F.F.A.F.F.F.{.F. XF..XuX.X X XF.uXuX XD. XuXrXrXiXiXrXuXtXrXyXB.N.B.N.F.F.B.N.F.F.N.rXI.U.I.rXa.< X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X . X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X % X X X X X X X X X 4 D 0 0 5 5 5 8 0 q G T i D D 6 q i q 0 0 q 9 9 9 9 9 q 0 0 q q q F G Y Y R R %.&.,.2.:.*.=.! W T *.,.0.2.(.(.q @ X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X o X X X X X X X X X X X X X X % X X X X X X X X X X % X X X X X X X X X X X X X X X @ X X X X X X % X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X # N b.sXiXiXsXhXsXkXhXzXiXyXI.N.N.N.F.F. XuXF.#XF.D.F.D.D.D.A.F..XD.F.D.F.F..X#X.X.XD.#XpXpXuXdXhXiXhXhXsXhXpXiXhXiXyXiXiXrXyXrXF.F.D.N.n.F.N.F.N.B.B.z.z @ X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X > X X UX ", +" UXX X X X X X X X X X X X X X X X X X X . X X X X X X = G r 9 9 9 5 6 6 9 T =XD i i 0 q 0 q q 9 0 q 0 0 q 9 0 0 q r q q 0 T F K Y D R &.!.0.&.=.=.&.%.T W *.&.!.2.(.=.V.4 X X X X X X X X X X , X X X X X X X X X X X X X X X X X X @ X X X X X @ X X X X X X X X X X 7 U X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X & . X X X X X X X X X X X X X X X l K.yXsXsXyXiXsXsXiXyXU.rXrXiXuXuXiXpXiXuXrXuXuXpXuXpXG.F.M.F.F.#XOX#X#X#XOX#X.X#X.X#XA..XF.D.{. XhXzXzXzXzXhXhXpXaXpXhXhXhXpXrXuXuXuXiXyXuX XF.m.F.D.F.m.N.B.N.N.v X X X X X X X X X X X X 4 X X X X X X X X X X : X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X T r 9 5 9 4 5 5 8 0 D F D q q 9 9 9 9 0 9 9 i 0 q 0 9 i 0 q 0 q q q r F H ! G G ! ,.=.T &.*.&.K G &.*.*.! ,.(.,.*.,.. @ X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X U 3 X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X f s.U.yXyXU.B.iXzXhXzXiXyXiXhXjXnXnXnXhXhXaXhXrXN.N.N.x.n.N.n.b.F.}.A.#X#X#X#X#X%XA.OXOXOX#XOXF..XF.F.D.A.}.#XpXjXjXzXzXhXpXtXpXuX X XD.N.F. XuXyXyXuXrX.XF.F.G.G.F.F.F.F.G.s.f @ X X X X X X X B X X X X X X X X X X X w X X X X @ 6 X UX ", +" UXX X X X X X X X X X X X X X X X X X X X . X X X D T 6 5 6 4 5 5 9 9 9 0 0 r q q 0 9 0 9 N r 9 q q q 0 0 q i F q 0 i Y G Y T R F K R &.i r q Y &.K K T R ! &.%.=.(.*.,.U . X X X X X X X X X X X X X X X X X X X < . X X X X X X @ . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 3 X X O X X X X . X X X X @ > X X X X X X X X X X X X X X X X X X a.U.U.U.I.B.B.B.rXyXsXhXzXzXzXnXzXgXbXjXJXlXuXuXuX XG.n.b.b.b.n.N.F.F.#X.X.X#XOXOXA.#XOXOX@XA.A.%X#XA.A.F.D.m.F.F.Z.D.{.aXlXlXaXpXhXhXiXaXtX.X X XF.uXyXpXuXyXuXrXuXiXrXrXG.G.F.F.G.F. .@ X X X X X , X X X X X X X X X X X X X X X X X X X UX ", +" UXX X X X X X X X X X X @ X X X X X X X X X X X X 4 R r 6 5 4 5 5 6 5 5 6 9 9 q i 0 q D q D G i 0 i q 0 0 r 0 D G i Y R T Y K W R R G ! R q i i D U W K T R ! T =.=.,.*.&.!.q X X w X X X X X X X X X X X X X X X X X @ X X X X X <.9 X X X X X X X X X X X w w X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X > X X f I.yXsXU.U.I.I.B.B.rXB.B.iXzXzXzXzXzXG.n.n.n. XK.N.N.n.b.v.s.v.s.v.v.z.D.}.}.A.}.D.D.A.A.A.S.`.#X#XS.A.OX#XOX}.}.D.m.x.m.D.m..X}.pXoX|.{.oXdXpXpXuXhXuXaXuXuXpXhXhXuXuXuXuXuXrXF.F.F.N.F.N.N.N.N.g @ X X X X X X X X X X X X X X X X @ X X X X X UX ", +" UXX X X X X X X X X X X @ % r X X X X X X X X X &.Y 9 5 5 5 5 4 4 9 8 9 9 0 r i 0 0 0 0 i G i i 0 i D i r 0 q q q Y T R T G K *.Y K D q 0 6 q r i D F r K Y F T =.2.,.2.*.2.$ X X X X X X X X X X X X X X X X X X X X X X X X X X % @ X X X X X X X X X X @ - 4 X X X X X X X X X X X $ X X X X X X X X X X X X O O X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X . X X X X X X X @ # Z yXyXU.I.B.B.N.B.M.B.N.rXK.yXiXiXaXuXF.rXyXN.b.N.b.b.v.b.b.z.v.v.D.F.F..X}.#X#X}.Z.A.A.A.OXOXA.OX@X].OX%XOXOX`.#XOXOX#X}./.}.D.D.uXpX X{.}.}.$XD.}.oXaXhXpXhXjXuXF.uXpXhXhXuXrXrXuXaXuXF.G.F.F.D.D.N.N.m.M X X X X X X X X X X X X X X X X X X X X X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X % X 5 %.i 9 5 5 5 5 5 5 5 9 9 9 9 r F 0 6 q i i q G D D q F q D q q i 0 q Q ! W K i G ,.&.i i q q 0 0 r N i D K T G K J.*.,.2.,.*.K X % X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ > @ X X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X N X X X X X X X X X X a.sXyXU.B.B.B.rXB.B.B.M.N.iXrXrXiXyXF.rXpXN.uXF.b.b.v.v.z.b.m.m. X.XoX%X$X@X%XoXOX}.A.V.A.A.V.A.A.C.`.S.S.S.S._.S.`.`.OXOXOX}.OXOX}.{.b.K.N.x.m.Z.{.}.A.Z.{.}.oXpXhXjXpXuX.XoXpXhXuXuXuXuXuXrXrXrXuXF. Xm.F.D.N.m. .X X X X X X X X X X X X X X X X X X X UX ", +" UX# X X X X X X X X X X X X X X X X X X X % : X Y R F 9 5 5 5 5 5 8 8 8 9 6 9 r I q 0 i G &.T K K q r D i F i i q i q r G ! &.&.,.! T q q q 0 q r q D q 0 &.=.T R Q 2.0.,.!.!.!.= X X X , X X X X X X X X X X X X # X X X X . X X X X X X X @ X X X 5 % X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X H X > % X X X X X X X X # d.yXyXU.U.B.yXyXiXyXB.M.n.yXzXpXhXzXuXF.F.m.D.z.D.A.A.OXOX#X#XaXaX$X}.OX#X|.#XoXOXD.A.A.A.A.A.A.A.A.`.'.].`.].`.`.C.A.S.C.~.OXOX`.}.}.}.}.A.F.uXiXuX XZ.Z.Z.A.Z.A.Z.D.A.}.$XdXhX.X XoXpXiXuXuXpXjXaXuXuXuXrX XF.D.F.F.m.n.' @ X X X X X X X X X X X X X X > X UX ", +" UXX X X X X X X X X X X X X X X X X X X X X 8 ! ! G 0 5 5 5 5 5 5 5 5 5 0 r m D i D q G %.R R *.G K G Y Y D D G q q N K *.,.!.! *.G q 9 9 0 i q i r q 0 F ,.&.0.2.!.!.,.!.).=.T @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 0 0 > X X X X X X # d.sXiXU.I.B.I.rXrXI.G.M.B.iXyXhXhXjXcXxXaX%XOX#X%X#X@XxX-X$X$X%X$X+X%X}.$X%X$X.X}.A.D.c.F.A.A.V.A.A.A.%X@XOX`.`.A.A.A.A.`.A./.C.C.C.A.`.}.OX}.OX}.{. XhXuXN.x.c.Z.^.A.^.A.Z.Z.Z.Z.$XpX{.F.fXpXpXuXuXpXiXjXpXuXuXpXrXuXF.F.F.n.b.x.) @ @ X X X X X X X X X X X X X UX ", +" UX& X X X X X X X X X X X X X X X X X X X o %.*.T F 9 5 6 6 5 5 5 5 5 9 m H H G i 9 r T %.R T *.%.W Y R K K G F D Y *.! ! *.,.! K q 9 9 9 0 0 q i q q 0 9 T =.,.!.(.2.!.,.).2.,.o X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X @ X X X X X X . X 3 @ X X X X X X X X X X X X X X X X X X X X X X X X X X % % X X X X X X X X X X >.!.% , X X X X a.sXsXU.I.B.yXrXyXiXN.M.n.G.iXhXhXpXaXlXfX%XOX#X%X%X%X@X@XxX%X@X$X$X+X#X%XaXaXaXoX#XoX.XD.m.m.m.D..X.XD.A.A.}.A.Z.A.Z.A.OX}.A.}.`.C.C.C.C./.C._.`.+X].+XOX{. XuX{.x.f.x.Z./.A.#XA.D.D.oX{.$X.X}.dXpX.XD.D.uXtXpXpXhXaXpXuXiX.XF.N.D.F.x.x.M X X X X X X X X > X X X X UX ", +" UXX X X X X X y > X X X X X X X X X X X X X 4 W W W T 6 5 5 5 5 8 9 5 8 0 D T Q T q q G %.! ! ! ! *.! Q T ! K G G Y R T ! ! ! R H F i q 0 0 0 q D i q q q 8 K ,.0.0.J.(.(.!.(.0.*.N . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X # ] X X X X X X X X X % X @ @ X X X X X X X X X X X > X X X X X X X X > 3 X X X X . X X X X X X X X X X X X k X X X X X X X X X X X & & X > X X Z yXU.yXU.rXB.iXhXyXiXG.B.n.rXrXhXrXrXfXcXcX%X%XfX1XaX@X-X X X X X . X X X X X X X X X X X X X X X X X X X X X X X . X X X X @ X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X v yXU.U.yXrXI.rXB.iXzXaXB.rXhXnXbXaX XuXfXfXaXfXxXxX%X s X X X X X X X X X X X Y T R ! K 8 5 5 : 5 6 8 5 5 i q K R F K G G i K Q &.~.!.R Y R ! K D D q 0 r G D q 0 q D 0 F F q 0 q q 0 0 q q 0 J *.(.!.!.,.(.!.!.0.&.0.* X X X X , X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ < rXyXrXyXN.N.F.rXG.iXhXnXjXnXnXnXnXcXnXcXcXcX%XaX%X%X X X X X X X X $ X X X X X X X X X X X X X X.yXU.U.B.N.rXuXuXuXlXHXnXnXnXnXnXbXcXnXcXnXcXcX%X#X#X%XxX@XOX`.].@X UX ", +" UXX X X X X X X X X X . e X X X X X X X o %.R G =.&.D 6 5 4 5 5 5 5 5 5 9 8 0 q q D i D K T R R Y G q 0 i D 9 9 0 F K T R K H q 8 9 D i G R K Y U D D 0 q q &.Q T T ).,.,.*.#.*.,.!.,. X X X X X X X X X X X X X X X @ @ X X X X X X X X X X X X X X X X X X X - % X X X X X X X X X X X % X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ d.yXI.I.G.pXaXxXaXaXxXnXJXnXnXnXnXfXcXxXjXaXpX-X1XxX%X1X X X X X X X X X X X X X X X X 8 ! T K 5 &.i 0 5 5 8 6 5 8 5 9 8 5 9 q 0 i i G r G Y T ! K q r F i q 9 D F ! Q %.W %.R K D G =.%.R T 0 6 0 9 9 0 8 0 i i 0 Y !.!.2.U D i ! ,.F X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X d.iXI.rX.XaXaXxXxX%X%XcXnXJXJXbXcXxX@X@X@X%X%XcXcXcX1X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X 3 sXyXB.yX.XaXaXaX%X@X%XxXnXcX%X@X].%X%X@X%X%XfXcXbXcX1XOXS.S.xX].S.xX.Z C Z C C C Z Z a.a.X.a. .a.Z a.| Z _ _ .| .h.h.j.j.h.j.j.j.h.h.f.s.z.UX ", +" UXX X X X X X X # R.r.e.9Xe.5.8.<.5.q.q.e.8X6.D G G *.Y E.e.e.,Xe.5.8.5.8.q.r.r.GX5.6 8 0 q G Q T Q.,Xe.9.8.5.q.q.q.r.t.! ,.,.,.!.).).0.&.,.J 9 0 6 6 9 9 9 9 9 9 0 F G R 0.%.q 0 i Y %.X X X X X X X X X X X X X X X X X X X @ , 4 X X X X % X X X > X X X X X X X X X X X X X X X X , X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ z iXrX.XMXKX,XeX;XA.A.S.V.1.j.mXKXLXu.~.mX[.u.A.A.S.].`.`.A.Z.A.OX`.;X;X].;X;X;X;X3.;X;X;X;X;Xw.;X_.;X;X;X'.;X3.3.S.w.S._.w.S.C.V.V.C.V.C.C.C.C.V.:.V.vX^.XXk./ ` ` X.o.l.$XV.2.~./._././._.~.`./.A.!.~.~.`.`.~././.A.V.:.&.{ l.} M C Z .Z n C C C | | Z X.a._ _ Z _ _ | .a.| | .M a.f.f.j.a.h.f.j.g.f.f.a.UX ", +" UXX X X X X X X X o : : N MXr.5.8.q.i.u p r q q q F G G K p H #.mXr.5.8.e.y.p w 5 4 4 4 6 0 i K &.i p L.mX:X:XGXi.~ p J D R =.0.0.(.,.!.2.%.Y N 0 r 0 9 k 0 0 i 9 r R G %.,.*.q 0 G ! Y X X X X X X X X X X X X X X X X X X @ - X X X X X X X X X X X X X X X X X O X X X X @ X X X X X . X X & X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X | yXG.uXE.KXLXi.A.A.V.@.' m m I P #.MXGXr.q.e.t.1.1.C.:.Z.A.V.A.C.C.;X;X;X;XS.3._.w._.;X;X;X;X;X;XS.;X'.S.S.;X;X3.3.3.S.S.S.S.C.3.V.V.V.V.V.V.V.V.V.1.,.-X$X-.^.` C ^ V V k.$XoX,.:.V.!.C.~.`.`.~.~.~.C.C.C.V.C.~._.~.' V n b n n V b V C C v n C v Z C C $.z. ._ Z Z Z Z Z | | _ _ A M ' | a.f.a.a.f.f.f.f.O.a.UX ", +" UXX X X X X X X X X X # X H.mXr.8.q.7. = - 4 5 0 D F G Y q i 8 H.KXq.q.qX6.. . = = = ; 5 9 q G Q i 0 q F MXLXt.5 5 4 9 0 K ! &.*.!.2.(.!.,.! D q i 0 9 m i q q q q i G D =.,.Q F &.! Y X @ * X X X X X X X X X X X X X X X X X 1 o X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X d.rXG.uXhXNX,Xi.Z.1.@.' m ' m +.+.' #.{ MX>Xq.q.y.E I +.&.V.V.A.V.A.S.'.;X3.;X3.3.;X'.;X;X;X;X;Xw.;Xw.;X;X;X;X'.S.;XS._.3.S.3.3.3.V.V.V.:.:.2.V.1.:.:.:.-.*.+XvX$Xl.{ V V C ( ^.-X:.*.j.V.,.V.~.~.`.-X+X+X~.^././.l.-.{ V C v c n b b b C v v n n n C | .[ m.d.C / | Z Z Z Z Z Z Z V Z | | | | . .a.@.@.f.f.$.X.UX ", +" UXX X X X % X X X X X X & MXr.8.r.i.; o = ; 5 G G G G G D i N N MXe.8.e.i.. $ = - 9 5 6 q i &.K q 9 i Q.6Xw + = = 4 q F %.>.0.!.!.0.!.!.,.R K i q q q H q 0 0 i F Q Y ! ,.&.&.*.&.%.O X X . X X . X X X X X X X X X X X X % r = X X X X X % X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X % X X X X X X X X % X X O O X O X X X X X X tXU.rXaXaXMX>XeX[.f.M m m ' E :.@.3.1.1.*.>.HX>Xe.t.6 w ' :.C.A.S.`.S.S.S._.'.S.;X3.3.w.;X;X;X;X;X;X;X;X;X;X;X;X_.S.;Xw.S.S.3.C.:.:.:.:.2.V.:.:.:.:.&.@.&.&.&.&./.*X$X|.V.P V V -.$X}.^.,.:.^.|./.V.}.%XOX|.-.P L c b c b b b v b v c b v v v n v v n C X.| O.x...Z Z h.d. .Z C C C Z Z Z M M Z | ` | .a.o.f.j.f.$.UX ", +" UXX X X X X X X X X X X X # R.3Xe.e.5Xu O $ 4 i F H T T K G H R G E.3Xq.r.6Xy . o 4 q m 6 i H q &.i D q #.MXy.; = + 1 8 F &.!.(.0.!.0.(.0.(.).2.,.G i i i ,.0.q q q U F D i 0.*.W Q T ! X X o o o > # o * o # o o @ @ X X X X X O O X X % X X X X X X o + # . o + o o @ o X X X X X @ X X @ % X X X X o . o o $ . o $ # . o # o o # X X X X X X X X X @ , yXrXyXpXaXiXHX>X6XV.m m m m ' @.A.S.S.S.S.S.V.Y.KX;Xt.- : e 1.S.S.S.'.'.'.;X;X_.S.C.3._.'.;X;X;X;Xw.;XS.'.;X'.;XS._.w.S.3.3.3.C.2.@.@.:.:.&.&.:.-.-.&.+.+.+.+.&.*.V.oXD.Z.^.-Xj.B L $X*X}.^.}./.!.c.}.^.#.L P V C V b v B C b b v b B B B V C C V x b C X.O.[ x.g. . .) h.m.f.) Z C Z V Z Z Z Z Z ) Z a.Z .@.@.j.;.;.UX ", +" UXX X - X X X X X X X X X S NXr.e.>X7.. = 5 r &.&.W T G Y Q ! 0 T.BXr.r.DX7.. . 8 G 6 q N r i D q q p T.GX5.q 4 4 5 i T ,.(.!.(.!.!.0.&X&X[.(.,.T N N &.!.,.Q G G J D q 0 ! >.*.G T T X $ 4 5 4 4 5 5 ; 4 8 4 5 4 4 $ @ X o X X X X X X X X X X # . 4 4 4 5 4 4 5 4 5 ; * X X X X X X X X X X X X X = 4 4 4 4 4 4 6 5 4 4 4 4 5 ; + @ @ X X X X , X a iXU.yX.XaXxXE.,Xr.u.+.m m m ' C.S.S.].].Xr.>X6X+ + 5 D =.0.!.=.! T Q F 6 E.3Xr.r.,Xi.o $ 5 9 0 0 i G r i 0 i Q Q.6XG q i 8 i K ! ,.2.2.2.(.0.(.[.).0.=.4.&X2X8X3Xe.&.W &.i q N 9 6 D %.=.K Y H o 5 p J p J J J J p J J J p p 5 4 + # @ X X X X X X X X X @ 4 p J p J J J J J J J 4 o X X X X X X X X X X X o 4 p J J J J p J J J J J J J p 8 4 : . X X X X 7 iXrXrX#XaXxXxXE.>X>Xi.m 6 0 m 1.A.].#X.%.! ! W W I M V ^ [ L V P #.%.#.! %.! N Z Z V V #.*.>.l.K.c.h.h.X...c.h.x.h.a./ / R %.#.%.#.#.#.Z Z Z | f.j.g.;.UX ", +" UXX X X X X X X X X X X X o R.0X>X>XeX5.+ 4 q %.(.&.*.! T *.K N MX3X3Xr.>XwXy $ 4 0 D q r q q q q 0 A NXy.! i q i T *.2.,.,.,.!.(.).(.J.).mXKXDX8X5X8X8XqXLXeX9.N g 0 0 9 9 D %.T R 0 2 MX3X3X3X,X>X3X3X3X>X>X3X,X0X8X9.5 ; * X % X X X X X X X s NX3X7X3X>X[.3X7X,X,X3X6.X X X X X X X X X X X X 2 MX8X7X3X>X>X4X4X>X3X,X>X>X3X9XVXr.p ; * # X h iXrXrXaX%XaX%XxXMX>X,X5Xe 1 m I V.].].;XX>X,X4X,X>X,X,X,X>X5XqXr.~.S.3.3.1.1.1.1.1.2.1.1.1.2.:.:.:.&.R.KX6XW T { T { R { %.R >.NX7X4X>X>X:X,X7X7Xu.N c c c E.4X4X>X3X[.>X7X0X6Xm z ' j...O.E.9X,X>X>X3XqXeX~ Z .NX4X4X7X3X4X5X~ ) h.f.h. Xc.z.f.d.NX0X,X>X>X>X7X9Xt.n Z Z X.x...;.UX ", +" UXX X X X X X X X X X X X X X S NX3X>X3Xt.+ = q G &.! Q *.>.K Q H.DX,XVX>X>XDX7.o = 8 K i q 9 0 6 q 0 T.GX9.R H N G J *.&.=.!.2.0.(.=X(.E.KXqXGXeX9.Q *.=.mXNXDXqXeX5.6 9 8 9 F F Q *.5 2 MX0XLX8X>X>X7XeX7X3X4X4X4XDX0X5XGX5Xu > @ X X X X = X X 2 NX0XKX3X>X>X3XGXqX3X0X5.X X X X X X X X X X X X 2 MXqXLX8X>X>X7XGX3X,X4X4X4X7XGXVXqXLX5X5.; h U.I.rXaX#XaX%XxXxXQ.7X,XDX7.5 m +.C.S.OXX>X7XeX7X4X4X4X4X7XLX0XqXeX5X1.1.1.1.1.1.1.:.*.:.:.:.:.:.&.-.MX8XeX9.F F P Y { j.-.+.&.NXVXLX7X>X>X7XGXGX5X+.@.-.( E.0XSX7X3Xr.>XGXLX6X+.@.1.V.+.h.R.KX8X3X3X3XGXwXQ j C NX7XVXSX>X>X,Xi.P .O.m.uX-Xc.O.k.MXLX8X3X>X7XGXLX6.n C C X.h.;.f.UX ", +" UXX X X X X X X X X X X X X X @ Q.8X>X3X6X: = 5 i R &.%.=.,.! J W.6X:XMX3X>X4Xi.. = 4 0 0 i i 9 q i q Q.6XQ Q G D Q ,.&.,.0.(.,.,.(.(.mX7XqX6X0.! i F G G Q D P.KXqXeX6.8 9 q i D W U X $ w - MX,X,XeXu p p i p p w #.MXIXVXqX6.* X X X X X X % $ = i - MX,X,XGX7.w p p + X X X X X % . X X X X X O w = MX,X,XwXu p p p p p i : #.mXKX0XGXu.K.N.rXaXaX#XaXxXaX@XE.DX,X,X5Xu 9 E V.A.S.;X.l.R ,.l.! ^.P.KX,X,XwX8.&.:.j.l.^.&.V./.A.MX7X>X3Xy.W E +.E *.1.@.` b N Q.LXqX6X~ f 7 g g j Y T Q.7X3X3X0Xt.c b } z.c.m.k.$.a.O.E.KXqX6X~ 7 d d l V ..g.X.c.UX ", +" UXX X X X X X X X X X X X X X X T.IX,X,XeX5.$ 4 q G ! Q &.0.&.#.MXy.p E.0X3X4XwXu $ 4 8 0 9 G 9 N 0 #.NXy.*.T G i ! !.,.!.!.).!.!.(.MX8XqXi.,.=.p 0 6 q i Y N i p.NXDXGXt.8 q D W Y i X X X E.,X,XwXy X $ . R.IX9XDXt.* X X X X X X X X X o Q.3X7XwXy X X X X X H e ; X X X X X X = E.,X,XwXy X X o X S MXDXDXu.c..XF.#X%X%X%X%X%X$XNX7X,X7X5X9.I &.A.A.A..2.!.R.GX5.5 H.NX8X,X0Xt.. ; 8 6 6 9 9 0 q T.eX9.&.F r H *.=.0.=.,.2.0.,.MX9X0X6X(.! G q 6 9 4 q G Q N 6 H.KXVXDX6.8 F T Y 4 X > X # R.7X7XwXy X X o 2 MX8X8XeX5.X X X X X X X X X Q.4X7XwXy X X % X @ @ X X X X X X X $ R.7X7XwXy X . # N NXDX9Xi.j.D.A.#X%X@X%X@XOXQ.0X7X4X7XGXqX[.S.A.S. 7 7 c L ...W.wX4XKX9X9X5XJ N c a.$.( V n C ^ W.wXp = * , < 7 z V X. ...UX ", +" UXX X X X X X X X X X X X X X X X Q.DX9X9X6X: = 5 D G R T Q =.W.6XQ 9 N MX9X8X9Xi.= 4 5 5 9 0 0 0 6 Q.i.&.! F G #.&.,.&.&.,.,.0.Q.VX0XeX9.! i q 5 5 5 8 q Q &.#.i 4 Q.DX0X5Xu 0 D F o X X X X o E.9X9XwXy X X X o Q.9X7X0Xi. X X X X X X X o Q.7X9XwXy X X X X X X X % X X X X X X X o E.9X9XwXy X X , d.H.KXVXGXt.f.A.A.%XxX@X@XOX&XNXDX7X7X9X9X9XVXDX0X4X[.'.].@X].`.`.S.V.1.:.*.:.V.C.V.2.-.E.9X9XwXy # = 1 m I I E I I U NX9X8XeX5.7 k { { { +.{ +.-.-.R.eX8XNX0X0X6X; , F :.j./.^.l.Z.l.Z.|.NX0X0X6X+ = 5 i E 1.1.1.E +.MXVX9XeX5.% 5 r ~ *.m v [ [ R.wX1.7 % * , j P ` O.;.W.wX! R.IX9X0Xi., c O.} ..b b v z W.wXp * # , 3 f l ` ....h.UX ", +" UXX X X X X X X X X X X X X @ X X T.IX0X9XeX5.+ 5 q G R T Q #.MXy.G i 5 E.DX8X9XeXu = 4 0 F D i i L.NXy.&.K G J =.&.*.,.,.,.0.J.NX0XDXi.U N q 5 4 5 6 q D ! ,.! i 5 H.KX9XVXi.8 0 D X X X X # E.9X9XwXy X X X o E.9X7X9X6X; X @ X X X X o Q.9X0XwXy o X X X X X X X X X X X X X X X * R.9X0XwXy X X X X X > K.v.h.Q.GX9X5X~ @.A.#X%X#X#XOX].E.IXDX9X9X9X7X8X9X8X8X0XDXqX[./.OXA.V.A.V.:.:.:.2.,.:.-.&.Q.0X0XwXy X % 5 H T +.+.+.T #.NX9X8XDX7., 0 P T -.{.}.&.&.@.Q.6X8XQ.DX9XeX5.= N -.l.-.,.2.:././.V.Q.DX0XeXu % 6 H E *.*.1.E J.KXDX8XVXy.$ 4 H +.m j L L ..Q.i.V k % , < k c ^ h.m.W.wXG , Q.DX0XDXt.7 7 h v ` a.v l W.wXp > , < l z b .O.a.k.UX ", +" UXX X X X X X X X X X X X X % X X S NXVX9XVXy.$ = 0 q &.E ! R.eX5.q 0 6 H.KX0X0X0Xt.= q K T G K ! R.wX0.,.K Y &.%.=.=.0.0.!.(.Q.0XVXGXt.q 4 5 8 9 q q G Q ,.*.,.R D q MXVX9XeXu 8 = X X X X X $ E.0XVXwXy X X X X X o R.0X9X0X6Xo X X X X X X @ Q.0XDXwXy X X X X X X X X X X X X X X X X # E.9X0XwXy X X X X o tXB. X{.Y.KX0XVXu.I :.A.A.OX#X%XOXA.R.KXDXDX0X0X9X9X9X9X8X9X9X9XeXe.V.V.V.V.j.:.:.@.:.:.%.@.E.0XVXeXy @ = 0 U { +.+.+.+.#.MXVX9XGXt.$ 4 0 P -.bXfXZ.{ L.KXt.&.H.KX0XVXi.= k ' V.^.%.^.!.%.;.:.R.KX0XDXt.O 5 H +.*.:.@.+.W.GXKXVX0X6X: 4 m m j j ^ V H.NXt.N m g 7 7 v ` } X.o.W.6XR 7 S NXGXDXeX5.< g ^ _ z.^ z W.wXp 4 7 7 h V C [ o...d.UX ", +" UXX X X X X X X X X X X X X X . X X Q.FXVXVX6X: = 6 D K Y H W.6XJ 5 D 0 i MXVX0XDX6X4 0 G W &.,.!.Q.6X(.*.W W =.,.*.(.(.(.(.(.NXVXDXwXu , = 4 q D i D i %.,.(.W &.Q Y E.GXVXVXy.= . X X X X 2 E.VXVXwXy X X X o = MXVXVXGXy. X X X X X 1 Q.DXDXwXy X X X X X X X X X X X X X X X @ E.VXVXeXy X X X X sXI.rXpX#XXXNXVXVX6Xe ' :.A.OX@X@X%XA.2.k.NXPXGXGXFXVX0XVXVX0XVX0XVXGXu.+.:.@.:.&.@.:.@.&.o.%.Q.VXVXwXy o = 8 F U { { { +.;.NXVXVXeX5.X = 7 { }.bXbXCX$XW.wX(.P l MXDXDXwXy g I l.&.%.V.%.>.j.c.J.NXDXDX6X5 6 m :.V.V.:.+.MXi.Q.GXVXDX6.1 w 7 j M [ z T.wX=.N k k ..} k ^ _ [ .W.wXp 7 2 H.KXGXDX5Xm g / ` ) C b W.wXN 3 f x V C | _ O.[ X.UX ", +" UXX X X X X X X X X X X X X X X X T.IXVXVXGX5.+ 4 q K T #.MXy.p 0 6 K D Q.LXVXVXeX5.; Y R !.(.&XNXy.0.Q Q &.,.(.2.(.=X).).P.KX0XVXwX6 $ = 8 T K K i r *.(.T ,.%.,.D T.IXDXDX6X+ X X X 3 E.VXDXwXy . . X . o # o = A NXDXLXwXy X X X X @ = Q.DXVXwXy X X @ X X X X X X X X X & X X X X @ W.DXDXwXy X X X n.rXiXpXaXF.A.NXDXDXeX<.r +.A.OX%X@XOX`.A.:.' #.mXKXIXGXLXGXGXDXVXDXVXSXGXt.T W +.@.+.-.o.{ +.{ Q.DXDXwXy * = 8 B { ' U { +.L.KXDXLX6X; . $ 7 } oX*X}.ZXgXMXi.&.D 9 E.LXAXGXt.= m -.:.l.}.+X+X^.l.c.Q.LXDXeX5.3 I { &.l.l.Y.KXt.L.KXFXGXi.; , 7 c ^ z f MXi.h 7 7 g P j k j V [ V W.wXG 7 7 7 R.IXFXDXu.4 g b v V b W.wXi < 7 k ^ ) C O.| Z C UX ", +" UXX X X X X X # X X X w.X X X X X X s NXSXVXFXi.$ ; 8 i K T.eX9.G i 0 i i L.KXSXDXAXu.* q *.,.(.Q.wX[.).*.! &.0.(.(.!.J.0.0.Q.SXDXDX6X- $ 4 9 K Y =.*.K ! ! %.! T F D H.KXFXFXwXy X X X X # E.SXVXwXp 5 5 5 5 5 4 4 N MXLXLXeX5. X X , X 5 - @ Q.FXFXwXy X 9 @ X X X X X X X X X X X X X $ E.SXDXwXy X X a.rXyXhXaXaX%X#XMXSXVXGXt.1 m j.A.`.OX`.OXA.V.-.m 6 % - L.mXIXKXLXLXLXDXVXAXGX9.F U +.+.{ { +.{ #.E.FXFXwXp 8 8 9 F U U U { ( Q.LXLXGX5. $ 5 ] }.-X+XZ.MXGX9.U q k #.NXFXFX6X= 9 I { J.#.>.-.j.^.J.Y.KXFXFXi., m T -.V.V.W.6X1.o.MXSXFXwX: , / P c h H.NX6.f 7 f g f L f.f...] V W.wXp 1 < 3 d MXLXGXGX9.7 k _ ` _ W.wXT 3 < f _ X.) _ [ O.X.UX ", +" UXX X X X X X X X X X X X X X X X X o Q.LXDXVX6X: + 5 i Y Q.6XJ 0 5 q D &.#.MXAXVXVX6X; 8 J J.).MXi.&X(.&.Q ,.!.).(.0.=.,.,.Q.DX0XVX6X= + ; q K *.*.,.T &.,.%.K F J D L.NXDXFXeXy X X X o Q.VXVX5XQ J J J Q J 4.mXKXLXLX6Xp X & X X @ @ Q.DXDXwXy X 4 X X X X X X X X X X X X X X X o E.VXVXeXy . o C U.yXaXaXaX#XA.xXMXVXZXFXy.- r @.C.A.OXOXZ.A.A.V.:.E m 4 = @ o = J.mXIXLXVX0XDX6X0 k P ' +.{ +.{ { E.VXVXwXQ J Q Q ! ! %.{ -.MXIXLXGX6.* = 7 N @.] { @.Q.6X%.F L +.J.Q.LXVX0X5.8 k j.l.c.j.c.J.^.l.Z.MXDXDX6X: 8 I V.Z.(.MXi.@.Y E.GXDXGX6.= , g B V W.wXY 7 7 < P ( c ` f.$.O.$.W.wXY g 7 h g p.KXLXVX6Xu 7 ) _ ^ W.wXK 4 7 f c X. .( _ ..g.UX ", +" UXX X X % X X X X X X X X X X X X X X T.KX7X4XGX6.. 4 q #.MXy.q 0 5 9 Y =.%.E.0X,X4XeX5.4 G ,.&XNXu.(.,.%.%.,.(.(.(.0.0.,.0.Q.7X3X7Xi.o . 4 G %.&.*.%.%.&.Q Q G D *.&.L.NX7X7XeX5. X X X $ R.8X4X0XqX7X7X7X0XqXqXqXeX5Xu o X X X X X X X @ @ Q.8X8XwXy X X X X X X X X X X X X X X X # E.8X8XwXy 2 I.rXhXaXaX#X%XxXxXMX,X,X7Xi.% 5 I j.A.`.`.A.`.A.A.V.+.' m k 5 1 = * . S MX0X4X8XeX5.5 k P { { ' } } E.8X4XqX4X3X,X4X4X7X9XKXIXeXLXwX~ 4 + o o , g F U { } L.NXi.f.U k k { R.KX4X7Xy., 0 { -.g.-.oXoX*X$X^.Q.0X7XqX5.4 F :.Z.P.GXe.1.' L.NX7X7Xi.o * 3 7 N MXy.c 3 7 7 g ^ [ ` ` d.[ ..W.wX%.g 3 7 / C T.KX9X8Xi.5 7 L ^ W.wXK < < j v / a. ._ | X.UX ", +" UX% X X X X X X X X X % X X X X X X X s NX8X7X0Xu.. = N T.eX9.i 9 4 0 G Q *.L.NX7X7X9Xt.; q ! W.wX[.,.*.! 4.,.(.(.!.(.(.).(.Q.7X7X9Xi.. $ 5 G %.*.%.,.,.>.,.4.! ! *.#.L.NX8X8XwXu X 8 X # E.8X8XwX4X9X9X9X-XSXNXLXGXeX5.* X X X X X X X # Q.7X7XwXy # # X X X X X X X X X X X X X X $ E.8X9XwXy X K.K.iXaXaX%X%XxXxX%XMX7X7X8Xy.@ , m 1.C.A.A.A.#XA.A.V.j.f.&.+.I m 0 6 1 1 8 MXDX7X8Xy.= 7 F / ' { ] U E.9X7XqXqXqXqXqXqXqX0XDXGX5X~ N 5 , * # = 8 k U ' } { W.wX!.{ F D U l.l.MX0X8X6Xt 7 ..l.!.{.oXoX-XdX}.L.KX9X9Xy., 9 ' V.Q.6XV.@.' I MXDX9XwXy , 3 h H.NX6.h 3 3 7 h x x x / C / [ W.wXp 7 f j v ^ L Q.qX0XGXt.4 h L W.wXJ 3 < g ) O.X.X... . .UX ", +" UXX X X X X X X X X X X X X @ X X X X o Q.0X,X7X6Xy o 4 Q.i.p G 9 5 q D &.=.&.MX3X,X7X6X: 4 ! MXi.(.,.%.&.0.0.0.0.=X(.).0.!.Q.qX4X7X6Xo . 4 i W *.&.,.0.,.,.0.&.&.&.Q H.KX7X7XwXy @ O % @ R.8X4XeXy H p p - . T.KXDXeX6.$ . O w . X X @ X X @ Q.7X7XwXy X X X X X X X X X X X X X X X X # R.8X4X6Xy | b.uXpXxX%X@XxX 9 L } ' ] ] E.7X8XeX9.i J Y T +.! R F N 9 7 4 = = = 4 k P P U U #.MXi.,.&.Q *.,.,.J.E.9X7XeX6.4 I V.|.^.l.$X^.XX^.l.MX8X7X6Xp 5 ' l.NXi.Z.+.I +.R.KX7XGX7.= 3 8 W.6XU z d 7 g j h h h h v V [ W.wXG 7 7 k z ) m A NXqX8XqX5.8 k W.wXK 2 < d L ..a.X.../ .UX ", +" UXX X X % X X X X X X X X X X X X X X X H.KX4X,XqX6.O S NXy.i q 5 6 q G T *.&.R.DX,X,XqX5.; L.NXt.2.,.%.&.,.J.(.(.0.,.0.2.0.R.PX,X,X6X: . 4 H >.*.,.*.,.0.0.=.&.*.&.K R.0X>X4X6X: X X X @ R.4X4XwXy X T.IX8X5Xu X - E O X X & X o X E.4X4XwXy X X X X X X X X H.u.X X X X % O @ E.4X4XeXy , _ m.pXxXxXxX%X A MXy.N g < < 7 d l z c c v l v W.wXp 3 < h V C ` } H.KX5X7X5Xp k W.wX%.7 7 7 x V | . .| _ UX ", +" UXX X N X X X X X @ w X X X X X X X X # s NX,X>X4Xi.p T.eXu 0 9 6 0 q G Y ! ! >.NX4Xr.4Xy.p T.wX0.=.Q &.&.0.J.[.0.0.0.,.(.!.J.NX,X,XwXu . 4 i &.=.*.,.(.(.).J.!.=.Q Q Q.,X>X5Xi. X X X $ R.,X,XwXy X S NX>X,Xt.. X O X X X X X X X $ E.,X3XwXy X X X X o % X Q.i.X X X X X X o R.,X,X6Xy X a | D.#XaX%XxXxXxX-X%X$XMX,X,XwXu o = k @.Z.Q.6Xj.@.@.f.j.:.j.:.@.-.-.-.@.+.{ { P R.8X,X6X: + 7 c P ( o.#.Q.[.>X5X5. o = 7 k m m L F 8 7 8 7 8 k N P U U U ).Q.qXqX7X7X3X4X4X3X7X8XqX4X,X5Xu = P ;.{.{.l.XX-X(.c.&XNX,X4Xi.% 8 Q.6XJ.;.+.D 5 x Q.7X,X5Xu > H.GX9.N g 7 7 f g l c z c B x v W.wXp 7 g k ` [ [ } Z W.IX,X,Xy.> W.wX=.k 7 7 d b .) .. .` UX ", +" UXX X X X . % X X X X X X X X X X X X o # Q.4Xe.r.6X8.Q.i.q 0 8 0 i i i R Q ! ! Q.3Xr.r.6X8.MXi.&.&.Q Q ,.(.).&X0.(.(.).0.(.(.MX>Xr.qX7.. 4 i ! *.&.,.).=X(.0.,.=.&.F MXr.r.eX6. X X X o R.r.r.wXy X s MXr.>Xi.O X X O X X X X X X E.r.r.wXy X X X X X X 1XY > MXi.X X X X X X # R.e.r.wXy , < X.D.%X%XX6X+ # , 7 ' $.;.g.E.r.r.eX5. # 1 0 ( ( @.{ >.' ] P P ( P U U U $.^.kXNXi.:.*.*.*.*.,.V.,.=.P.KXr.>X7.= 9 ] c.^.^.dXfX*X/.XXMX4Xr.6Xy S NX7.l.&.q 7 c L T.KXr.r.7.t W.6XP h 7 1 d j L V V c c c P Z W.wXF 7 g c } o.} O.O...Q.4Xr.5X6.W.wXp k k h c ) _ O.` ` / UX ", +" UXX X X X X X X X @ % X X X X X X X X X H.KXq.q.5Xe.NX7.; 6 9 6 F Q K K Q &.&.R.LXq.q.5Xq.SX6.T ! #.&.,.0.).0.,.0.(.!.!.,.0.R.KXr.r.i.+ - m Q ,.,.!.).).(.,.&.%.K H.mXr.>X6X: X X X X # R.e.e.6Xy X X X X # 2 MXr.r.6Xy . X X X X X X X X # E.r.q.wXy X X X X X , s MXy.X X X X X o R.e.e.6Xp % f @.A.%XxX%X%X%X%X#X.XE.4Xe.GX6.@ @ = m ..j.Q.3Xt.P P I +.-.:.-.+.o.o.@.+.{ { { ' R.e.>Xy.+ @ 4 k @.$.k.c.E.e.e.eX5. , k ' {.-.f.{.l.^.|.c.c.g...} { o. XlXW.wX-Xg.P m m W -.U { %.-.NXe.r.6X: 9 ' l.}.V.l.{.D.j.{.R.SXr.DX7.T.wX0.P 5 3 7 g g B NXr.q.i.8.NXy.g 7 7 3 7 f.c.] $.[ g.$XoXk.W.wXH 7 7 k _ ..X.X.} g.d.NX>Xr.5XmXwXp h 7 v c ) ` ^ . .X.UX ", +" UXX X X X X X X X O 1.X X X X X X X X X X 2 MXq.8.8.r.6Xu 4 8 q K &.&.! ,.! Q >.NXq.5.q.e.6Xp t G G >.0.(.(.0.=.=.,.=.,.!.,.&.MX>Xq.5X5.; 9 J ! (.!.!.).2.*.%.K i Q.r.q.eX6. X X X X R.q.q.wXy X X X o 2 MXq.q.5X5.. X X X X X X 9 @ E.q.q.6Xy X X X X X X . H.KX7.# X X X o R.q.q.6Xp % g @.A.%XxX%X#X#X#X{..XMXr.r.i.1 @ $ , m ' f.Q.r.i.I k m ' -.j.@.;.;.o.{ ' { ' :.;.Q.r.GX7., = 7 ^ f.x.c.c.E.q.q.wX5.@ , k @.^.{.XX^.g.{.|.}.x.c.k.g.-.P { {.Q.i.^.-.P k k P -.:.@.$.{ E.r.q.5X5.4 P -.V./.}.(.{.{.c.J.NXr.r.u.MXi.3 , , < 7 g g h Q.>Xq.5Xq.eX5., 7 7 j ( $./ ..$.o.g.XXdXoXW.6X%.k 7 j ^ / } ../ } O.H.KXq.q.e.wXp 7 k m z j v v C ` .UX ", +" UXX X * X = X X X X X X X X X X X X X X X W.>X5.8.r.i. 4 i K ! =.&.=.! &.=.Q.r.8.8.r.y.= 8 i Q =.0.(.!.(.!.,.,.,.,.,.,.*.L.NX>Xe.i.; 0 G >.!.,.=.=.#.R T q H.AXq.r.i.. X X X o R.q.q.6Xy X X > NXq.8.r.7. X X X X X X X X W.q.q.wXy . o # X X X o & Q.GX7.X & X o R.q.q.6Xp , 7 @.A.#X%X.XOX}.{.{.E.SX3XwX~ , * * 7 m o.j.Q.q.:Xy.7 k ' @.g.j.x.o.{ { o.{ ] U -.NX,X6XT 4 1 g ' c.Z.{.l.W.q.q.5X5.o = k ;.$XgX.Xm.m.D.x.k.c.c.o.{ ;.o.J.Y.KXu.^.>.o.I { { F l.+.-.-.L.KXq.r.y.- k +.V.^.}./.^.{.l.J.Q.r.8.q.,X7.* > > > < 3 a f H.NXq.q.r.i.; * 7 v P / P f... .a.O.h.c.J.Q.wXR 7 7 g c ^ ` / V L m c Q.r.8.e.6Xt 1 7 { c x z z z C Z UX ", +" UXX X X X X X X X X X X X X X X @ X X X X X H.NX8.q.GX7. o 9 K ! %.%.&.! =.&.R.KX9.q.eX6.. - 9 G >.,.0.!.,.,.0.,.2.,.=.J.0.,.R.KX3XDXt.4 i K R >.! ! T Y q S mXe.>Xi.y X X X . * # R.q.q.5Xy . ; . X X X X MXq.5.q.i. > MXy.o X X = $ E.q.q.wXy = ; ; ; ; + # . & H.KXGX5.X -.o + * R.q.q.6Xp - 9 { A.}.#X.XD.}.D.E.SX1XwXq.0 = % 1 7 ' @.j.Q.q.q.qXt.k I f.K.Z...-...} { { ;.-.E.,XGXt.k 7 1 k } ..Z.x.c.E.q.q.eX5.. 4 x g. XXXm.m.m.c.x.m.k.Z.x.f.@.g.$XQ.wX(.l.;.P P >.l.|.*XV.!.^.).MXq.8.6Xy g { ^.}.$X^.^.^.l.l.R.KX8.q.6Xy # * % > 3 3 f s N MXr.8.eX7.X # > , d k z v h v N V _ B Z Q.eX~ 0 9 g V ^ ` V ^ L V } B MXr.e.6Xy 7 h @.....C V c b z UX ", +" UXX X X X X X X X X X X X X X X X X X X X X 2 MXq.q.wXy X o R T Q *.&.,.*.! >.MXq.q.i.: = 9 I *.=.0.,.!.=.0.,.,.,.,.,.0.,.#.P.KX4XGXt.6 i F K T Q F q p.MX3Xr.i.y X + t ; E.q.9.5Xu ; t ; X X X X W.:X8.8.6Xy . s MX7.X + ; ; Q.8.8.5Xu t t t t t ; ; ; P.NX>X6Xu X # X ; t ; Q.q.q.6Xp p p W ,.A.D.D.{.oXMXIX1X6Xw.P 7 % = 7 m ..@.f.Q.q.q.e.VX6X~ ;.h.} { $.{ ] -.j.g.E.KX,Xi.^ j 7 7 m I g.c.-.c.E.q.8.qX7.; t B x.XXc.{.oXoXc.k.J.c.c.m.j.g.-XE.KXDX9.o.] { :.l.V.^.(.-X&.:.}.E.8.8.4X7.4 F %.Z./.Z.Z.l.l.-.k.MXq.r.i. o # * < 3 d f l N W.r.q.6Xy o # > 3 d d f h ' g l x g H.mXr.t.4 2 7 j V ) ` V / ` B c L.NX,X6Xt 4 m @.c.Z.$.( ) ` / UX ", +" UXX X X X X X X X X X X X X X X X X X X X X o E.e.:Xy.. X 6 Q %.,.,.&.%.&.Q.r.>Xy.. + 5 Y &.(.,.,.=.0.J.,.2.,.,.=.=.,.&.! H.mX,X[.5X9.t J p J U E.KX4XGXu.- X X o # L.[.mXmX8.8.e.5X,Xq.5. X X X X S MX:X8.qX6.; . W.GX5. > P.=XmXmXq.8.7Xu.8.9.8.8.8.2XmXAXe.q.q.6Xy L.[.2XmXq.8.DX3X8.8.8.3.8.=XBXKXKX7XGX6X:.m m 1 , 7 k ' o.@.k.Q.,X5X8.MXIXLX5X8.T $.Q -.l.gXbXMXIX7X6XV.M 8 7 9 L ..x.Y.[.mXHX8.9.e.5X,Xw.9.g.k.c.g.#.Z.m.k. X Xc.m.L.mXmX,Xq.e.eX5X1.*.).(.(.g.^.!.>.P.mXmX8.8.q.qX8X[.e.>.l.l.l.+XgX=X#.Q.,XGX7. X > < f g f } k.L.KX:Xi.. X , < 3 f g ` g.l.P k.Y.mXSXe.q.DX0X,Xu j j V Z V ` ..^ ^ k T.IXwXy , m -.^.oXc.;.f.^ / UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X H.NXLX7. X X X = 0 W 0.*.*.*.R.IXeX5.. o = m W ,.0.(.,.,.,.,.0.*.,.!.,.2.,.%.Q i 5 P.KX4X,XDXe.q.=XVX,XLX6Xu . X X X X 2 NX,X:Xe.q.q.q.[.:Xr.7. X X X X S mX:X,XeXDXNXLXy. s NX,X:X[.e.e.e.r.e.q.q.q.q.>X>Xr.r.q.r.6Xy s NX>X[.e.e.q.:Xr.q.q.q.q.w.r.e.,XGX5X=.} m j 1 1 7 k ( o.@.f.c.Q.LXt.H 8 H.KX,X,X9XVXe.8.[.AXPX8XGXu.j.o.m 7 f m } j.c.NX:X[.[.e.q.e.q.[.:Xy.` $.o.o.P ..m.{.m.k.c.fXQ.,X:Xe.q.e.,XGX5.8 0 B P P U U J.NX,X[.q.q.q.[.>X,X6X*.^.|.|.AXBX|.] H.KXwXy o o , j b C g j b z.{.MXGX6. X , < f g v k {.;.-.g.NX,X[.e.e.r.[.,X7.k j L N V L f.$.f.] N Q.wX: 1 m V.$XjX|.m.g.o.[ UX ", +" UXX X X X X X X X X X X X X X X X X X X X & X > MXwXy X X X X 5 ,.T ,.=.#.MX6X6 + + 4 D &.>.0.,.0.,.,.,.*.,.,.,.&.*.*.Q T %.i 8 - #.mX9Xe.q.>XeXr.; X X X X O : : : : y : : : : + X X X X $ L.mXVX,XGX6.8 , O : : : w : : y : : : : y : : w : : : O O . $ : P K.h.{ D w w w m +.j.V.V.1.+.' M m j 7 7 7 k M +.f.k.x.m.W.6X;.F 1 1 5 L.mXKX7Xq.q.e.,XGX5X~.j.@.I m k k ( o.k.k.k.^.W +X).N : : : : 5 3 N N B B { k.m.c.{ ;.|.ZXCX-X+X,.=.R D 7 4 / f.#.,.}.&X+X=X!.!.p i w : w w K Y ,.|.lX-XCX!.] ] MX6., * = 7 P ( L z c k g.;.Y.5X6 $ # * j ..f.( O.*XoXg.f.h.l.-.l.I D D w w 8 7 ..c.{ f.f.o.L ` l.X.o.' k k I l.#XbXdXuXpX{.g.UX ", +" UXX X X X X X X X X X X X X X X X X X & X N , . & : X X X X X X = i %.! &.*.Q 6 = 1 6 Y *.!.0.,.,.,.,.,.&.=.,.*.&.*.&.Q Q Y 0 5 4 . . : : : : . . . X X X X X X X X X o X X . X X X & X X X X . + : + X X X X X . . X X X . K.v.| ^ 7 = % 1 j M ' M M M M m m j 7 m z m ' O.Z.D.m.D. XK.^.:.P g 7 k k m m P T l.:.j.l.j.X.X.L P m j m ' f.k.' P { g.+.>.4 = $ $ $ = 4 3 8 c N P } $.g.$.g.BXFX|.-X=X(.{ D 7 = 7 g P B :.|.(.l.! W l.T m , @ . , 0 { T ^.BXBX).] ` ( [ L 7 * % 4 B L f.c.m.c.U o.XX(.T 7 4 , g x..Xm.$.^.vXgXg.g.{.c.D.P c = 1 7 7 P g.$X*X}.{.{.@.o.$.k P P m m +.Z.oXfXbXdXjXnXdXUX ", +" UXX X X X # X X X X X X X X @ X X X X 9 j X X X X X X X X X X X { = 5 K ! J r 6 0 G &.0.0.,.(.2.,.,.*.,.2.*.,.Q *.! %.U D 5 4 = . . X . X X X X X X X X X X X X X X X X X X X X X w X X X X X.n.s.| ) j 7 7 f m M M M M m m m m m m M ' ' o.x.D.D. XuX X.XF.:.' P P ..@.' ( L ' ( L { ' ' ' m k ' L ' O.x.K.oX-X>.l.*X#.q 4 9 k 4 = 1 7 7 0 k N B P U ] { f.^.*Xl.-X=X(.>.T 9 , , 4 8 ,.|.>.>.=X+XU ).V.m 5 1 = 5 m +.K (.).R L f.g.} X.[ k 7 j ..X...x.K.} g. Xf.}.|.^.@.L 7 c ' g.f.f.f.m.oX{.^.XX.X{.g.-.' L I { :.^.=XcXbXzXJXvXgXvXoX@.k M P o.}.$XaXjXbXnXnXbXUX ", +" UXX X X X X X X X X X X X X X X X % X X & X X X X X X X X X X X X X X N X X X 4 D N q F %.!.,.J.(.,.2.2.,.,.,.,.,.*.! Q &.K 0 0 = X X X X X . X X X % X X X X X X X X X X X X X X X X X X * # X @ X X X X X O X X X X X X X 2 U.N.v.h.O.' ) ' ' M ' ' ' ' M ( ^ ) ' ' ..@...f.D.pX X X$XuX{.k.f.l.o.@.;.l.f.f.f.j.o.' ' ( ' ( V M ` ' f.f.f.oX|.AX*X=XgX+Xl.g.-.l.' F g 8 7 g k B B B B B B U !.gXCXgX*X+X!.-.P 7 7 8 N -.l.-.!.-X%.-.`.-.@.&.P P @.I *.).{ F N $.;.c.z.d.N k L B c o.$.+.{.J.$.{.^.c.c.^.k.f.;.} } X.c.c.g.x.XXdXbXdX XD.o.@.>.j.,.J./.+X-XbXbXCXJXZXJXJXHXfXXXO.' / k.jXbXjXbXnXnXlXUX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ = @ X = 5 T ! %.2.!.%.&.&.%.*.Q Q R R D q 5 $ X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X w X X X X X X X X X yXU.I.M.n.z.d.z.a.a.a.a.@.a.@.X.@...;.x.@.a.z.;.j.D.lXrXK.{.g.oXoXdX.Xc.o.j.c.{..X{.Z.j.l.c.-.o.j.X.} X.h.;.j.oXkXZXAX|.ZXZXBX-X4X-X=X-X).N P B N k x B / B %.(.-.|.ZX^.=XgXgX}.c.l.U $.^.l.l.}.+X|.|.>.^.-Xl.+X(.!.$.>.j.l.).K $.+.{ { P L L L c c ( ' V P } g.XX{.c.gXdXuXdXK.D.dXg.h h j O.k.g.l.dXnXdX{.{.XXg.;.!.^.{.%.|.{ |.CXJXbXJXbX*XHX=X{.D.{.c.O.pXhXnXnXJXnXnXUX ", +" UXX X X X X X X X X X . X X X X X X X X X X X X X . X X X X X X X X X X X X X % X X X X & X o o 4 8 K Y 8 5 8 6 > ; X X O X X X > @ X X @ X . X X X X X % X X X X X X X X X X X X X X X X X X X X X X X 8 X X X X X X X X X X X X X X X X X X X X X O X X X X X & X X X X X X X X X X l U.I.N.N.n.z.n.m.z.z.z.h.Z.h.h.h.f.z.{.m.z.f.g.z.}.bXCX.Xg.z.oXfXHXVX(.O.g.x.;.oXfXdXaX-X-XZ.O.c.k.x.o.;.U f.oXbX^.}.lXCXAXBXdXdXjXvXbXZXgXc.*X$.N N F N P ] l.*Xj.BX=X=X=XvXZXjX^.vXgX^.gX*X=X=X|.^.-X2X2.-X+X!.(.-X-XV.Z.|.j.f.^.|.$XZXl.k c x z x c ( X.{.c.c.;.;.` k.gXlXgXoX|.dXoXB L c.g B O.f.uXlXbXc.c.{.gXR B o.$X-.{.J.o.$XZXCXJXZXXXl.' D.dXpXdX{.uXdXbXnXJXcXpXUX ", +" UXX X X X X 4 , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X r X X @ X X X X X X X X O X o X X X X X X X X X X X X X X @ % & X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X sXU.I.N.N.N.m.n.z.v.x.x.x.v.z.z.z.x. X{.z.f.f.k. XlX-X{.m.h.x.}.*XgX^.$...X...h.pXhXpXbXgXc.;.k.g.o.f.;.P P $.{.fX=XCXdXdXJXCXkXuXjXCXHXbXAX^.-.{ L L N L ] ;.$.U #.vXBX=XBXSXJXJXgXgXlX{.{.lX^.-.>.>.%.! ,.gX(.{.*X).).XXZ.*XbX&XBXAXFXFXBXc.g.j h ^ f.f.k.XXj.J.$.[ B { |.{.hXbXCXdXc.k.P c.] ..x.{.bXfXoXc.$XoXuXgX*X).dXvX^.gXfX^.j.Z.|.oXCX$XXXpXbXdXnXbXbXbXbXnXbXhXhXUX ", +" UXX X . X X X X X . +.&XX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - - X X @ X X X X 4 X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X Z U.U.N.n.N.n.n.b.b.z.x.b.x.z.j.z.Z..XaX.XD.x.g.z.z.{.N.z.d.x.c.;.c.c.$.....f.O.c.jXjXdXdX{.{.k.c.$.U o.] ;.;.-.BXBXjXfX{.dXZXkXuXoX|.dXgXZXJX*XN B N N N U {.oX] $.-.fXCX).-XSXFXFXJXgX*X>.k.|.>.>.>.!.*XXX=XZXZXvX^.bX-X|.^.gXAXAXAXHX).^.{ ;.c x ` h.} ;. XdXlX>.} x g ] |.c.c.c.m.$.L ..P .Xc.o.k.m.c.>.>.} ( { gXAXJXCX*Xk.L { g g c ( } P o.o.c.K.oXpXpXnXJXnXnXnXnXnXjXUX ", +" UXX X X X X X X X X & > @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X % X X . X X X X X X X X X @ X X X X X X X X X X X X X X X X @ X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X @ X sXU.G.M.M.F.n.b.z.b.v.x.x.z.h.z.x.{..XoXD.{.c.z.z.X.x.h.a.m.h.O...O.c.X.a.x.N.x.uXHXgXoXoXjX^.g.uXz.P k.J.{.c.$.=XZXZXZXoXdXbXXXlX*Xc.oXAXnXoXc.N N N N P } #.l.#.T #.vXbX-XJXSXDXAXlXdX.Xk.XXc.-.oXCXAXZX).^.-XgXBXV.|.vX=XJ.^.|.+XgXgX{ ` L z h g X.g.o.c.oXbXlXoX] V P fX.X{.k.N ] o.o.$.uX{.f.( V L c.oX*X&Xk.$Xk.c.|.+X|.B j V B g h _ [ L b / ..h.oXfXlXnXnXnXJXJXnXbXlXUX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X o X X X X X X X X X X X X % X X X X . X X @ X X X X X X X X X 3 X X X X X X X X X % X @ X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ .yXU.B.M.b.b.b.z.z.v.z.x.z.z.z.h.m. X Xm.D.x.c.m.a.}.m.h.h.z.d. ...x.h.O.$.uXx. X^.lXuX X X{.J.g.g.L P k.uXdXo.c.*XgXbXgXCXXX.X{.uXlX{.bXZXjXJ.|.R P k B F #.x.f.] -.=XgXBX|.ZXSXSXAXCXoX*XuXXXc.-.BXBXZXAXZXZXBXgXCXdXoX*X|.XX>.=X{.#.l.o.x h B g ) m.c.uXHXlXHXnXHXgX-.;.k.{.J.] l.l.P } o..X{ P ..B 7 j >.-XfXZXl.#./ { ..] c d h c l h ^ ` V j z ` x.gXdXcXfXnXnXnXnXnX#XfXUX ", +" UX> X X X X > X X X X X X X X X X X X X X # X X X X X X X X X X X X X X X X X X X X X @ X X X % % @ X X X X X X X X X 5 X X X X X X . X X X X % X % X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % , X X X X X X X X X X X X X X X X X X X X X @ sXrXB.M.M.M.b.b.z.z.z.b.z.z.z.g.F. XpX XD.c.x.tXm.d.*Xz.m.m.x.X.} h.m.O.$.g.c.c.uX{.oXXX.XXXc.] P P #.#.' g.{ ] c.^.oXdXoX^.x. X XoXpXoXgX|.oX*XJX|.#.-.T U B k.m.-.oXdX|.-X+X-XFXAXAX=XoXbX{.dX^.l.AXvX-X=XBXvXBXBX*X|.-XAXBX>.l.N F >.vX^.B V L V x.m.g. X$XpXjXHXJXAXfX;.P |.l.k j.oX{.x.K.f.;.uXuXU x U J. Xg.j.^.g./ L x g f a ) B V ` X.../ c f j ..|.pXc..XoXdXnXnXjX X.XUX ", +" UXX X X X X X X X X X X X X X & X X X X X { @ X X X X X X X X X X X X X O X X X X X @ X X X X X > 4 X X X X X X X X X X X X X X X X X X X . @ X X X X X X X X X X X X X X X X > . @ X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X 4 > X X X X X . . X X X X X X X X X X X X X X | iXI.B.M.M.M.z.b.b.v.z.z.z.h.h.m.F..XfX Xx.h.n.c.a.x.x. Xg.m.k.z.f.m.d.g.*XuX|.oX.X.X;.g.hXdXm.$.f.BX{.c.+.-.{ ;.$.Z.uXjXoXJ.;.{.K.g.{.XXuXc.c.lXCXfXoX^.=XgX>.-.>.J.HXAX*X=XBX*XSXAXAXAXBXCX-X|.l.:.vX*Xl.{ ,.).+XCXoXfXAXAXBX>.l.P { $.l.;.L L f.X.XXBXoXk.c.K.{.CXFXlXuXdX>.|.ZX%.j $.$..Xx.m.;.>.>.P c.lXbXBXXX-X-X{.^.;.P x l _ _ z z z h V / h L V ' h.;.c. XoXuXdXjX{..XdXUX ", +" UXX X X X X X X X X X X X X X X X X X X @ , X X X X . X X X X X % X X X X X X X X X X X X X X X 4 9 % X X X X X X X X X X X X X X X - X X X X X X X X X @ X X X X X X X X X X & @ H X X X X X X X X X X X X X # X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X sXI.B.M.M.M.M.b.v.v.b.x.z.z.z.b.uXF. Xx.s.a.a.x.a.x.h.h.h.m.c.oXtXn.x.$.=XAXlXlXlXdX$.x.dXjXXXXXXXl.$Xg.{ c.XX-.$.K.J. X.Xc.o.g.{.K.c.J.oXdX{.fXBXAXkXhXl.T #.] XX|.^.gXgX^.>.$X&XBXSXAXSXJXCXl.l..X{.=XbX=X*X(.|.*X-XoXJ.BXJXgX|.#.x L g.c.} $.] f.f.K.lX.Xk.k.c.g..XHXHXbXJX|.|.^.k.k o.-.m.uXN.{.c.o.$.XXAXAXZX$XgXk.{.XXdXl.[ a.X._ / ^ ^ L Z l V | V l l ` x.c. XkXpXhXuXuXbXUX ", +" UXX X X X X X X X X X X X X X X X X X X # X X X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X 4 X X X X X X X X X 4 X X X X X X X - X X X X X X X X X X X X X X X X X X X X X , , X X X X X X X X X , X X X X X X X X X X X X X X X X X X X C U.B.B.M.M.M.v.v.v.z.b.v.s.s.F. XuXb.z.z.v.h.h.s.a.b.h.s.z.m.g.kX Xx.d.K.bXAXSXAXAXmX{.sXhXhX{.lXsXm.uXm.oXuXk.$.g.k.lXzXJ.$.{ g.c.c.k.$.k.yXuXdXdXzXuXc.] U U >.f.XX|.XXXXg.g.k.>.ZXAXSXSXHXBXdXgXBXl.^.kXdXkXdXoXBX$Xc.dXdXgXJ.uXK.U V [ gXJ.-.{.k.k.;.m.z.$.k f ` XHXSXAXSXHXHX&XF g ( -.oXoXsXzX{.O.{ dXmXHXHXkX*X-...oX{.h.X.) h _ h h x.c.$.Z ` z h l x h.d.m.rXjXtX{. X.Xm.UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X 9 X X X X X X % X X X X X X > X X X X & X X X X % X X X X X X X X X X X X X X X % X X X X > X X X X X X X > X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X sXrXN.M.M.b.b.v.b.v.z.z.f.x.uXpXuXm.h.h.z.h.a.b.a.m.K. XrXm.$.m.oXlXx.f.oXJXAXAXSXSXlXJ.oXjX{.$.lXkXXXhXdXXXXXJ.{ } U k.*XfXXX$.{.c.J.g.k.g.k.J.{.-.*XdXJ.P R U {. XJ.|.XXT { >.c.*XZXAXSXSXAXSXAXBX*XXXBX*XkX^.l.|.CXfXXX&X-XgXgXHX&Xl.&.U {.(.L ;.c.$.[ $. Xl.B x B ;.HXAXJXZXSXBX-.j.L B l.&XhXgXHXnXoXx.J.{.bX|.l.N g j -.^.;.;.[ h c h f z d h d x l h f l V ..f.{.j.;.B ..m.uXUX ", +" UXX X X X X X X X X X X , X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X @ , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X . X X @ X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X o h yXI.N.M.M.M.v.b.v.z.z.z.F.uXjXuXm.z.a.a.n.O.s.z.h.oXkXuX XoXuXZXfXXXg.f.;.BXSXSXSXHXCXHXCXlX;.{ oXdXCXCXdXdXgX|.{ U c.#.-.XXCXoX{.k.$.;.*X+Xc.l.BXgX*Xc.T ] k.XX=XoX{.K.c.$.l.oX|.vXbXAXAXCXlXnXgXl.>.^.*Xl.^.&.J.+X-XBXgX|.gX|.-XgX*XdX+XoX|.-X-.J.c.L ] B { *X] B $.*XAXAXHXFXZX*XJ.>.N U -XgXjXjXbXlXdXbXHX-X^.k x x V x j / L f.$.( z a h a d h a f h c l h V k.f.P ` ( B f.k.m.UX ", +" UXX X X X X X X X X X X X X X O . X X X X X X X X X X X X X X % O X X X X X X X X X X X X X X > 4 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X sXU.B.M.M.M.b.b.b.z.z.F.jXuX XuXm.x.z.a.s.z.a.h.a.x.x.{.c.bXZXAXoXO.o...;.$XJXSXGXAXJXfXJXZX{.#.k.oXdXvXgX;.#.-.g.^.#.XX*XkXJ.-.{.c.-.*.&XCXkX-XBXAXAXAX*X&Xl.K.*X-XoX-.c.g.#.$.c.*XCXbXSXSXCX+X^.(.c.j.-.^.oX|.gXBXgX^.=XZX).SXZXlXCX&XgX=X*X{ ' -.[ B x V } ;.^.{.k.#.*XAXSXSXAXlXXXBX=XP l.|.-XlXdXnXlXZ.J.^.l.k c j g h -.$.[ k o.c h.P z v h f f a f l h x c ` [ $.V c [ B k.B ` UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X . @ X X X X H X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X , X X X X X X . X X X X X X X X > iXI.B.M.M.b.b.v.v.s. XbXhXpXuXF.x.z.s.O.z.h.a.z.h.uXz.z.z.oXgX|.h.` [ .f.fXJXSXDXSXAXoXbXHXgXoXoXlXlXdX^.j.^.*Xl.XXfXZXZXbX^.*X=X!.^.ZXZXSXVXBXAXAXFXAXAXAXZXCXZXoXAXl.{.g.c.-.^.vXdXJXAXvXgXAX*X#.:.J.Z.fX-XBXAX(.*Xl.gXBXl.AXCXCXJX=X XBX#.] ..c V x c ( ] B f.^.ZX!.@.$X-XgXAX=XfXHXvXgX^.{ ( g..XlXCXdXc...L c ( [ h ..L x g .` g v z l B v x l Z l h h f c V ^ c L ..O.c ^ [ B UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X # X X X X X * o @ X X X % X , @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X d.yXB.M.M.M.b.v.z.h..XAXlXzXdXm.z.h.z.h.h.f.a.X.O.a.x.z.z.a.[ } X. ...a.X.;.lXbXAXAXAXHXK.lXHXSXnXZXAXAXJXbXZXlX*XuXZXgXBX-XXXoX=X=XoXdX|.gX-XJ.bXZXCXFXZXvXfXXXoX*XCXBX{.>.l.gX*X>.J.bXAX=X^.*.|.! >.^.gXlXBX2X=X).|.oX).*XJ.^.gX=X*XHXAX*X-.oX{.Z.k.c c B c X.c L ] ^.l.N k.$.>.-.%.#.^.|.AX2XU c B L g.{.K.;.L j d ^ c L B h v z j v h g z f h h [ ] V C V V l c v x v / ^ h.$.f ..V UX ", +" UXX X X @ X X X @ X X X X X X X X X X X X X X X X X X X X X X X X # O X X X X X X X 2 X X X X X X X X X @ X X X X X X X X X 4 % X X X 3 X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X yXI.N.M.n.M.b.b.v.n.dXlXlXgXn.z.s.z.h.a.v.s.a.a.a.a.aXz.a.o.[ _ [ [ h.z.z.x.XXlXSXAXJX*XhX.XJXgXbXCXFXJXJXHX^.dXBXgXbXZXdX|.*XlX*XBX-X&XoXdX>.#.=XlX=XCXJXgX{.l.^.^.AX*XJ.g.J.CX-XoX|.=XvXCXZX2XfXJ.&.BXAXFXSXFXCXgXCXAXZXgX{.dX*XpX{.fXgXAX^.).BX*X*Xk._ f.V ] ;.c.).;.).k c -.AX-XXXR |.}.oX+XU B } g.a.{ ..L x f a z h c h l V v h l g _ L a.d.} ( B a h h b h v c h j V ` k.o.;.l...UX ", +" UX@ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . O # X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X % > X X X X X X X X X X X X X X X X X X X X o X X X X X X @ X X > X X X X X X X X 7 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X ^ rXG.B.N.M.M.b.m.n.oXjXzXlXc.d.a.b.a.h.a.z.s.X.a.X.X.m.X...o.( [ .b.g.h.z.h. XpXfXhXdXfX&XD.CXZ.pXbXJXJXAXAXCXCXfXdXjXvXg.c.}.}.>.^.vXBXgX*X{.*X=XgXgXfXAX^.|.{.XX{.lXl.-.|.lXBX^.|.Q VXJ.CX=X+X}.l.|.BXBX+XgX/.*XfXAXZXJXCXbXBX|.^.*X+X&XvXCX-X{.*Xc.c.;.] B [ X.] m.U oXg.{ BXBXm.=XJ.g.CX;.} P g ...g j b N c f 3 f a d b ` L v _ d z z L f.../ [ ) f j j V L x ` ./ [ L ] ..g.x.$.UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X 1 X X X X X X X X X X X X X X X X X X X X X X X 3 X X X X X X & > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X yXrXB.M.M.n.b.D.jXaXzXnXlXx.s.s.b.z.s.a.a.a.a.| a. .X.| .. .a._ | a.h.O.m.n.m.$.z.g.k.k.$Xk.k..X XdXJXJXJXAXAXCXAXHXbXZX*XdX(.{ f.-.g.=XJ.l.|.-XgXZX{.J.^.&Xl.l.oX{.|.ZX{.:.AXAX-XCX>.#.XXvXZXAXBX^.&.^.-X-.l.^.k.).BX-X-XBXAXgX*Xl.-.c.fX$X-XAXgXc.c.z.g. Xm.h.( } } o.( *Xc.g.|.l.g.o.] m.oXk.P h.B g g c g d d h h d d a d z X./ b j d x z V } B V B O.^ h ;.;.h.o./ f.$.} ( -.[ V k...UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & rXI.B.M.M.n.rXjXbXbXjX Xb.d.z.n.m.z.a.s.f.a. ._ a._ .z.z.O.X.Z | X.O.z.f.g.z.} [ $.x.-.k.} c..XuX$XoXBXFXCXCXoXjXfXbX$XJ.*XCX^.-.g.{ >.*Xc.l.^.^.|.l.J.XXoXJ.>.-.^.{.J.^.*XAX).^.CXl.-.|.=XAX*XJ.&.-.l.=XJ.oXoXJ.|.*X&XgXlXFXgX{.oXoX{.J.>.l.fXBXJ.{.m.m.m.m.oXm.m.f.f.O.] ( ] [ P } Xm.o.f.o.;.c ` [ h g j z c f h f f a a d z C z / d f l h X.$.` V h.m.f.O.c.....f.P z.;.V [ c _ o.c.UX ", +" UXX X X X X X X X X X X X X X X X @ X X X & X X X X X X O , X X X X X X X X X X X X X X X X X X X X X X X X X X X w X X X X X X s > X X X X X X X X @ X X X X X X X X X X X & X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ O X X X X @ = X X X a.U.B.B.N.n.F.lXhXjXhXuXz.z.d.z.m. Xh.h.b.s.O.a.) | _ [ m.x.d.a._ _ X.a.m.$.d./ ] ( ^ o.] / [ g.uXhXlXXX{.|.Z.oXXXpXlXoX{.&X=Xl.>.l.;.;.U g.k.j.XX{.J.oXJ.k.+X{.>.|.J.-.k.jXSX=XU ^.vX{.#.*XAX+XJ.k.l.c.{.XXl.HXAXCXCXlXdXoX$XvXZXgX.Xc.>.&.|.*XHXJXdXK..XkXXX X XhXuX{.dXfX*Xc.;.z.c...c.c.k.f.$...$.B C ..c g V j v j f d f < a d c V z 3 b V b [ B g g P ..D.m.{ ] x.#.a.o./ ` L [ ;.[ ;.UX ", +" UXX X X X X X X X X X X X X X 4 X X #.* X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O 6 # N & X X X X X X X X X X X X - : X X X X X X X X X X X X X X X X X <.# X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X @ X X X X X X X X X rXB.B.B.M.rXJXlX.XpXrXz.x.s.b.D.z.n.x.z.a.a.X.| ._ | a.h.m.x.O._ ` C ] h.o.X.Z V V ;.k.[ L o. X.XdXuXdX-.U ;.{..XdXoXc.m.}.}.|.g.ZXJ.c.K.XXl.XX{.*X^.uX|.l.#.|.k.XXl.k.K.vXBXc.#.(.vXc.(.ZX=X^.Q { l.oX{.*XoXdXgXZXHXoXdXl.{.{.dXoXl.oXXXJ.oX|.gXJXbXbXlXpXK.m.{.lXXXuXoXjXBXXXXX XuXk.$.m.$.$.K.x.L ;.B ..` c j L ..>.) b x 3 a a j {._ l f.c k.g.z 3 h } o.z. Xk.O.{.f.V V z b / f.f.f.f.UX ", +" UXX X X X X X X X & X X X X X X & X X X X X X X X X X X X X X X X X X . X X X X X X X @ X X X X X X X X X X X X X X @ X X X X & & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X # X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 3 U.B.B.M.N.lXHXuXuX Xz.b.s.m.n.z.s.x.D.x.a. .| _ .| _ ..a.m.a.a.[ _ V ^ z.m.d.X.$.>.XX[ V ( L o.;.o.{ k...g.uXg.XX{.oXdX{.{.XX|.-.-.c.XX{.l.g.m.c.*XoX{.J.^.k.c.l.|.$X*X*XFX).J.l.-X=X^.-X*X|.Q T Y =X|.k.{.|.{.-XCXfX XXX^.*XfXdXoXc.{.{.c.l.|.gXAXJXAXnXXX{.g.m.{.dXuXBXuX X XuXuXK.$.[ } ..k.g.g.z.( c.j ( V h f z g L h.M f < a c l.$./ g ..h ..L h c f.` } ;.x.f.x.P ..$.x V X.V h B a.UX ", +" UX& X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X > @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X @ X X X a.I.B.B.M.rXhXuXN.N.b.s.s.s.m.m.m.s.m.z.a.a.s.X._ [ _ _ .) [ O.d.X.L C ^ n. Xx.c.x.m.c.f.( B B V ..c B o./ $.x.L :.XX{.J.uXdXdX{.J.{.k.c.c.g.c.^.{.{.g.XXoXgXdX=X*XgXoX$XgXgXc.*X^.oXXXoXgX{.>.^.J.-XBX-.g.}.oX}.*XjXoXc.|.gXBX=XCXXXl.c.{.{.^.c.fXAX).ZXVXvXpXCXdXXXoXkXdXuXkX Xx.c.k.[ ] k.f.dX=X-.[ $.O.o.` j L f f z c [ .l a a g $.g.V x $.c b g b [ o.f.V b B X.c.B f.f./ _ [ j d j P UX ", +" UXX X X X X X X X X X X X X X X X X 7 X X X X X X X X X X X X X X E X X X X X X X X X X X X X X X X X X X X X X X % X X @ X @ * X X X X X X X X X X @ - X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X - U.I.M.N.B.G.yXF.b.n.z.b.n.n.m.m.b.z.z.a.a. .h.a.| | _ ) ^ _ ` h.h.O...b ^ x. XdXhXc.jXc.f...^ c ^ ..B L ` ] ^.l.f.U K.uXkXdXdX{. XJ.-.g.c.>.j.*X|.K.XX:.c.XX{.oXc.-.&X*X|.=X=XBX^.-XgXlXAXAX!.XX}.=X|.XXR J.|.oXgXnXlXl.U }.gX-XdXkXXX{.c.{..X{.J.+XJX).!.(.#.^.gX-XBXgXlXCXbXpXK.uXoXz.P V P ] |.*X-Xk.g. .} b ( ( x v d v L o.v f a h f.] V h [ z c / b ^ o./ V d l V O.X.$.L / ^ L } z.] X.UX ", +" UXX X X % X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X . X X X X X X X X X X X X X X X X & < I.B.B.G.rXM.rXF.b.n.b.x.N.b.z.F.n.z.a.a.a.X.z.| | ..) C C Z X.b.g.z.X.M ..z. XpX{. Xm.z.[ h.[ B V C / } ` [ f.B L J.{.kXdXJ.{.K.c.g.k.;.{.-.g.*XK.l.+.#.j.l.|.uXXXj.).JX^.{.=XZX^.AXJXFXFXBXl.{.l.>.|.|.l.|.oX$XlXCXZX{.l.gXAX*XgXkXl.c.K.Z.{. X^.-.BX).F ] L P }.JXAXdXJ.^.fXgXx.g.x.m.o.;.{ $.c.}.lXl.CXk.} c } h.^ b z d L ;.h g d b X.` z j a l V C ..b c z z f d h C B ....B ) V o.g.c.x.UX ", +" UXX X X % X X X @ X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X @ r X X X X X X X X X X X X X X X X X O . X X X X X X X X X @ 0 X X . X X X X X X 4 X X % X 7 @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X a.B.B.B.G.n.uXtXN.b.n.z.v.z.b.D.F.b.b.a.a.s.X.a...oXc._ ) C C [ h.h._ ^ ` X.z.F.m.K. XtXg..._ _ _ f.[ B ` ( L ` L L j.dXdXjXfXoX{.c.x.g.-.J.k.).BXgXl.k.-.c.oX^.dXc.+.|.CXgX=X*XBX=X-XZXJXAX|.*X}.*X^.=X^.{.^.{.oXBXCX-XpX>.=X=Xj.k.^.K.l.l.{.c.XXl.-.|.2XN ` ] L l.BXnXlX{ B >.*Xm.k.m.$.f.$.g.dXgX&XCX(.|.J./ / [ f.V h v a d O.f f d l m z z b d v h ` j d d a a f h l h d c C h ( B ` ..m.{.UX ", +" UXX X X X & X X X X X @ X X X X X X X X X & X X X X X X X X X X X X X X X X X X X 7 o X , X X X X X X X X X X X - X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X > X X . X X X X X X % X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X rXB.N.rXF.hXiXrXF.n.b.z.s.s.z.b.h.z.a.X.a.a.x.{.D.bX.X| _ n n V Z _ C b | .z.m.{.x.{.{.k.m.$.....$._ X.o.X.` ` V V P pX X{..X}.{.-.#.] ] =X-XdXfXXXJ.c.{ Z.^.^.gXoXXXvXZX=Xl.^.gXoXgXFXnXCXfXgXvXvXAXAXJ.l.>.^.BXgX-XlXdX^.P ^.{.J.k.-.J.c.^.c.}.dXc.-.*XY c h.P N c.-.|.l.N B { c.;. X$.} ;.l.CXgXdXCXgX>.l.^.x j ( z z a d d ) f d d l j z g.$.' L g...b f h z g v f l a a z g c } f.^ X.$.^.UX ", +" UXX X X X % @ X X X X 4 X X X X X X X X o g.X . X <.X % X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X % X X X X . X X X X X X X X X X X X X X X X X X X X X X U.B.rXF.hXbXiXyXn.b.b.b.s.a.O.s.O.O...X.a.c.zXlXhX X;.f.O.C b b V C ..L ) ` a.m.hX X Xm.m.{.h.O.O.z.a.O.a.h.} h.c L B :.oXXXJ.XX|.T >.^.-.(.^.oX=X{.oXj.k.gXgXgX}.k.-.^.AX*X{.oX{.{.AXFXFXAXlX*XCXBXgX=Xl.&.|.ZXBX*X*XlXjXfX-.>.l.c.j.j.j.{.fX*X*X{.c.-.*X*.c P c P f.B fXbXg.L [ L {.XXXX-.k.{.BX^.vXZXgXfXl.^.J.P f V g a a f d 3 ^ ^ h.g.X.l.*.g.c.^.j d f d v h f l a f V b h c ` O.` c C / UX ", +" UXX X I X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X X r X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X Z U.B.B.B.hXrXrXhXN.v.F.b.a.a.O.D.a.X.D.J.z.uXbXHXoXO.X. .C C z.h.| ` O.^ V _ f.m.uX Xc.m.h.O...X.g.b.z.X.[ ( O.$.c V ..P ^.*XoX&XBX|.+XXX^.|.-X-.-.g.XXJ.l.^.}.l.c.>.l.Z.vX}.J.oX X-XAXAXAXgXvXJXFXBX^./.^.*X&X+X=XCX$XbX=XoXfXl.l.l.j.j.J.{.oX$X{.l.l.-.+XU L ( c L ;.F {.oX{...] N @.gXjXlXXX-.-.*XgXbXdX-.j.] g.=XB c g ` 7 c 7 g c.c.{ K.k.g c ] z.L d f d g v z d f l v x.` z f z V V B ..^ UX ", +" UX@ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X @ X X X X X X X X X X X X X X X @ X X X X X X X X X X X X @ X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X yXB.rXyXrXrX XrX Xn.b. Xm.a.z.m.x.pXbXjXg.m.dXoX$XO. .a. .C C ) f./ n V n b _ [ x.rX Xm.d...[ d.^ O.$.O._ ` [ V c _ B }.J.}.lX=XV.-X-XCXgXoX^.*Xl.-.j.>..XJ.l.c.oX^.c.g.^.J.^..X*XfXdXZXbX-X|.fXCXFX=X}.|.|.-X-X=X-XCXCXgXvX*Xl.c.j.c.c.$XXX{.l.>.c.k.j.J.>.B B ..( B a.c ..{.m.;.] [ g.c.oXbXgX*X^.*X*X*XJ.{ ..|.-.gX|.z./ J.j.c h d o.k.' c.g.B / B f.d f a d f ` z f f l l h L o.V v b z $.[ j UX ", +" UXO @ X X X X X X X @ X X X X X X X X X X X X 4 X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X 7 & X X X X X X X X X X X X @ X X X X X X X X X X X X @ X X X X X @ X X X X X X X X X X X X X X X % % X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X rXB.rXyXtXN.N.rXn.b.n. Xn.s.uX Xa.m.x.N.n.oXk.( ` _ _ [ _ n n Z v ` ;.V C b L a.m.dXm.h.B ) . .` [ .^ / .d.V B V x X.g.(.-XvX=X+XZXVXCX*XgXAXCX^.l.>.*X*XJ.^.+X}.dXXX{.{.c.{.oXfXAXbXgXgXZXZXbXBX+X-X*XZXvXvXZXAXvXbXgX-X^.l.J.-.k.c.k.j.c.-.{ k.{ l.^.P j L o.` c k.P } / [ [ ..{ #.^.oX^.^.oXdXfXHXBX;.[ [ L [ k.vXc.d.k.c.o.[ z x ;.l.] g.K.] [ $.f d g j o.L _ L b l z [ ] ^ ../ [ O.h } [ UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X @ @ % X X X X X X X X X X X X X X % X X X X X X X . X X X X , X . X X X X X X X X X X X X X X X X X X X X X X . X X X X X X . @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ > U.rXrXiX Xn.M.n.F.N.z.s.z.b.F.z.X.....dXZXpX[ [ k.z.C C C n ) C _ h.hX{ _ L x. XrXuXx.[ c ) ^ ` ..f.f.( a.x.x.V l x ` $.N (.fXvXZXCXAXZX*X=XZX=X=Xl.-.>.$XgXoX}.^.*X}.}.{.Z.XXXX}.fXZXZXZXCXAXZX$X=XlXvXAXAXAXBXAXCX-XZXCXJ.l.c.dX$XK.l.k.l.k.-.{ U #.{.#.B j B P f.c } } [ c #.>.oXl.U R g.oX|.fXbX$X|.|.;.P X.[ ;.P dXJ.$.{.] k j ^ V d #.k.;.&.x } ` d d d _ B b L ..^ V d.( c z h h V x z ....UX ", +" UXX % X X X X X X X X X X X X X X X X X X X @ & % , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X .rXyXyXiXM.N.n.b.rX X XO.a.v.h.X.X. .z.dXnXK.^.lXCXX.C V ._ _ Z _ x.o.o.k.oXdXjX.Xc.` ^ ) ( c.;.}. Xm.$.m.m.o.B V ( z.B U *XdXdX).|.BX=Xl.oXZX^.{ &.-.j.&XvXZX$X:.^.jX*X$X{..X$XgXgX*XvXFXFXJX$XcX=X*XJXFXgXcXFXAXZXJXAXCX*XoX{.Z.K.j.>.g.l.l.g.;.%.j.{ N x k c c } c ..L j j !.|.l.:.Y Y -.!.-XvXfXdXXX{.J.f.B L h.k -XoX$.o.$.o...B o.c j V j ( ) h d a a d l l h n / c C L ( } z ` f x c / X.c UX ", +" UXX X X X X X X X X X X X X X X X X X % X @ X X & > X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ H X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X > X X X X X X X X X X X @ X U.iXsXB.N.F.N.M.b.b.N.K.s.z.m.XXiXuXhX{.f..XuXbXJXlXf.C O.hXk.O.[ .m.x.K.g.c.iXuX Xf.V b } |..XdXlX{...z.V N J.g.$. Xk.B ).lXdXpX-X=XAX:.{ *.vX^.{ >.l.j.fXbX*XoX-.>.$XjXoXhXuX$XvXAXAXAXJXJXJXgXBX0.vXFXbXdXvXnXZX-XvX-X*XZ.^.^.}.c.l.x.k.c.c.f.g.{ U / ( c b j k _ L B B k z %.|.{.Z.#.R R >.|.bXHXfXbXc.k.K.( / X.k *XlX{.c.k.L B ` } L [ f V z.^ f 7 a a d a f d n v v } v c / z V a.C x v ;.f UX ", +" UXX X X X X X X X X X X X X X X X X X @ % X X X @ & > > > 2 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X { X yXzXyXrXn.G.N.b.b.N.b.rXm.jXlXlXhXhXjXdXXXhXHXnXfXk.X.$.x.$.V V ` oXuXuXz.f..XiXhXrXf._ O. XCXHXHXoX Xm.B z z +Xc.z.g.P ' fXgXCXZXbXJXAXl.U T |.&X{.{.{.l.^.oX|.l.:.+.l.^.$X^.oX$XgXbXZXJXJXAXFXbXgXgXBXJXJXCXJXFXZXZXCX*X!.l.Z.Z.l.l.k.k.g.c.K.-.] ..B f.P c c x z b L X.` j N ).^.^.^.%.R U J Y :.).dXlXc.=XoXU V v g -.CXoXuXkXm...( ..` ../ g j ` f 3 l a a a a f f f ^ L / L f V l _ z [ V _ ` UX ", +" UX% X X X X X X X X X X X X X 0 X X X % X X X # U & > P ] s > & X X X X X X X . X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X @ X X X X X X X X X X X X X % X @ X X X X @ # sXzXiXB.N.rXB.v.s.b.x.uXlXnXnXlXhXzXzXhXhXzXfXpXO.` ^ {.K.h._ b.o.pXkXg.X.X.h.h.D.z. ...{.lXnXHXlXsXm...B c V c.-.c.z.;.^.fXJ.-XvXcXFXAX).G Y l.^.fXdXl.-.^.$Xl.l.>.%.-.+.>.Z.>.l.{.$X-XbXFXJXFX*XgXAXZXZXAXAXFXbXlXdX^.$Xc.l.>.-.:.j.c.c.g.k.T ( g.[ ] $.P L c z c c B / / j k ^.^.^.Z.^.l.Q N G G &.XXdXc.l. Xg.$.o...$.dXlXdXCXXXdXoXg.} } v C n l g d a f l O./ c z ./ } j ` ` V x h v ] ..V o.UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X > P ).2XW 7 > X X X X % X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ @ . X X X X X X X X 7 F X X X X X X X , X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X - X X X > _ zXiXrXN.M.N.b.v.v.v.h. XlXzXlXlXjX{.hXzX.Xz.f..._ n n C ^ V a.O.[ a.f.Z h.X.| / ..B / ` }.HXjXHXdXz.X.V c X.C ^ m.uXoXk.V.fX{.l.+X|.SXAX=X-.U ^.{.^.^.l.>.}.*X^.:.l.l.&.&.&.^.^.XX}.|.bXCXAXFXSXnXJXAXJXJXAXAXHXJXZX*X^.!.l.l.l.R j.g.k.Z.{.#.;.O.] ] ..$.L L B B L c c c j j k J.^.l.^.l.#.Y F K { &.oX|.j.$X{.k.f.f.c.;.l.oXlXnXpXdXkX&Xf.c b d ` f v d d a.c [ ..l f.X.V V ..v B v c b l b ) $.[ UX ", +" UXX X X X X X @ @ X X X X X X X X X X X X X X X X > k.AXHX[.2 @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X . X X X . X X X X , X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X z.kXiXB.M.M.b.M.v.h.b.h.z.x.hXkXpXh.a.m.m... .| Z ) Z C n ^ C C g.[ f.( ) V [ V ^ Z } x.$.x.{. XnXN.K.} z l c V O.{.{.#.P Z.gX-XlX*XBXSXAXvXR T R R -.>.l.&.l.oX#Xj.{.l.l.>.-.&.,.}.l./.nXAXAXvXBXFXSXSXAXSXSXJXnXZXbX|.|.l.-.c.l.{ -.-.c.{.#.{ o.P ..z...P L / V V v k c x g k k P ^.l.l.! J K F K R J.uX>.B ..} h.$.h.f.{.] c.J.hXCXkXuX Xm.c z f b V d ..l $.P d L d [ c V j _ [ c v l ) v c c V -.UX ", +" UXX X X X X X X X X X X X O X X X X X X X X X X X 2 Y [.1X=.7 > . X X X X X X X X X X X X X X X X X X X X X X X % % X X X X X X X X @ X X X X X X X X X X o 3 %.k X X X X X X X X X X @ > X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X hXhXyXN.v.M.M.rXb.v.s.s.K.pXlXpX . . . ._ _ Z | n Z n n C a.d.a.XXX.d.^ C v C b _ ;.oX/ ..z.x.hXlXm..X$.z l z / z.c.v c N +XZX-X^.-XvXAX).|.J.|.&.-.&.%.&.:.|.*X^.l.:./.|.%.:.%.-.,.l.^.OX-X*X/.bXAXAXFXAXAXAXbXfX$X).$X=X,.R { #.' -.m.m.-.-.k.V $.;.o.] Z V L L C c x j x h j j P ^.^.,.R G N G F T c.uXU ( ] ..B L L g.h.g.{.c.^.oXCXdX$.P ` f a z ..d v [ ] ] d o.B C v b z c b ^ ^ g l h C c j B UX ", +" UXX % X X X X X X X X X X X X X X X X - X X X X X & 2 P B s & % X X X @ X X X X X X X X X X X X , X X X X X X X - - . . X X X X X X @ X X X X X X @ X X o i =.3 X X X X X X X X X X X X X X X X X X X X X # s #.> X X X X X X X X X > =.X X X X X X X X X X X X X X X X zXhXB.M.b.b.N.rX Xz.s.a.XXzXlXf. .Z a._ C Z n _ _ _ ) a.| h.uXf.{.K.|.b._ b _ V ( m.{ ^ z.dXhXsX Xm.|.B z l c J.uX;.j c -.$X!.-.Y ^.$X+XdX/.!.:.l.%.%.l.Z.l./.gX^.>.&.^.ZX!.^.,.:.l./.}.|.*X-XZXFXvXbXBX=XCXJXbXfX}.|.-X^.T ;.R F -.D.J.-.P B V ] $.[ [ ] L L L .( B L V x x h h k { l.-.%.J F i F U XJ.B | h V x X.} m.} V O.P P j.>.|.} x d f a d ^ f L } *XgXU d d z z h z c x h L / f h x ( v C UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X & > > 1 X X % X X X X X X X X X X X X X X X X X X X X X X X O - X X X X X X X X 5 X X X X X X X X X X X X > X X X X X X X X X X X X X X X X X X X X X X X ).FX> X X X X X X X @ > X X X X X X & & X X X X X X X X X # zXiXB.M.M.M.N.G.rXb.m. XnXlXk.a.Z C _ C Z C C f.m.oXc.( d.z.g.N.uXuXoXd.g. .[ a. Xz.B K.jXbXCXzXoXN.B B l l { oXg.O.X.c.oX=X:.%.U &.Z.:.>.,.&.+.%.R ! -.:.V.l.|.}.l.%.:.*X!.-.)./.*XfXdX}.fX$XgXZXgXbXAX-XvXbXbX-X^.V.l.V.{ ! U G #.c.-.P B P ..$...] ^ / B / _ X.P [ / B B l h g j T { W ! U G i D U k.j.7 f d z g f.>.k.o.B o.[ ] ^ } P ] / B d b L B c.] k.J.dX).d f h f d [ z v h a o.v a l f / g UX ", +" UXX X X X X X X X X X X X X X X X X X , X X X X X X X & , @ @ X X @ X X X X X X X X X 6 @ X X X X X X X X X , X X X X X X X X X X X @ % % X X X X X X X X X X X X X X X X X X X X X X X X X X @ 9 X X X X X X >.A X X X X X X X X X X X X X - X X X X X X X & X X X X X ^ hXuXrXB.M.B.rXyXrXb. XjXjXn. . .A C | X.z.f.{.uXlXCXlXK.bXkXc.rXdXdX Xm.uX ._ XXXX;.|.$X;.l.oX-.g.L l ;.x x z V ( d.V ( #X_.,.+.T { *.&.&.%.%.&.-.-.&.V.+X^.|.$X{.V.:.l.*./.!.|.|.+X/.*X$XdXdX$XfXdXbXfX$XbX$X$X$X^.^.!.,.l.U F U ] -.{ P B ] } ] [ ] / L / ] ( X.[ h.$.B V z g f B #.{ %.W Y G N J g.K.B a h d d h j k.{.*XJ.f.$.} B d f d f g.P ` X.X.k.[ g.dXXXU a d a d f v b f l x / x h a l h h UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X & X 6 X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X % % X X 4 +.> X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X # X X X X . X X X # X X X X X X @ @ X X X X X > X X X X X a.kXrXI.I.N.N.K.N.b.b.iXtXm. . . .a.| [ K.uXHXCXiXiXjXzXiXgX|.CXpX$..XkXoXg.K.XXHXbXhX*XJ.C c.{.( ..B l z V d.V C _ B [ f.&.~.:.W T R *.-.*.:.*.-.*.&.*.,./.|./.$X}.}.!.>.&.V.+X=X$XvX).*XfXdX$X|.V.*XcXvX$X|.{.V.l.~.Z.&.+.^.N P T { #.] Z ] d...( ..[ [ ..} a.} $.k.x.L V _ c h g U -.W *.%.G G F j.{.l.g l v d j a f k -.lXlXlX Xx.o.j d f d k m.).c j j V ..{.dX$.b a f l l f h l h ) C o...d l [ h UX ", +" UXX X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X 5 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X X X X % X X X X X X X X X X X X X X X . X X X X X X X X & X X @ X X X X X X H X X X > X X X X X X X X X X X X X X X iXiXtXrXB.B.M.M.v.a.m.dXz.z.s.b.z.X.s. XuXhXzXzXlXHXkX XgXuXm.uXlXgXK. Xb. XhXCXzXlXJ.{ ^ ^ X.k.) z h l z _ Z C B V h ) c ] ~.=.%.*.W +.*.,.~.!.:.=.:.,.V./.|.|.pX|.|.}.V.=.{ :.{.).+X-XcXZXdX$X^.V.}.^.#X|.!.&.&.&.%.! -.%.&.D T T { ' #./ U [ ` ] o.$.m.{. XdXf.h.m.o.c / B V g B { +.%.&.+.W J U XX{.>.7 N f j b d a g c h.g. Xg.{.J.k.] h x d XXvXF z V h a.g.l.{.] l h C z h h C x h f _ Z g d .[ UX ", +" UXX X X X X X X X X @ X X X X # X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X - X X X X X X X X X X @ X X X X X X X yXU.B.B.M.M.b.M.v.v.rXn.z.b.{.N.m.XXo.m.hXkXzXHXHXjXHXzXkXsXm.z.{.g.x.Z [ XXlX|.vX^.X./ n a.d. .| z l l z v x V h V v v B L ~.,.:.&.:.&.,.~.}.@XV.2.=.*.l.C./.|.+X}.$X*X$X+X&.|.l.V.2.%XoX|.!.l.l.Z.*X^.{.-.R %.R W R T { #.K Y P U T { #.] ..] ( ....g.N.m. XJ.h.$...L c c L j c { { T ! ! T R T G X{.N a h f f ..l a 3 a g k } c c x.{.ZX^.L V ;.uXo.h d a f ;.x.[ f.$.a f h l f d h a d a a V f .j UX ", +" UXX X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X . X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X = X X @ X X X X X X X X X X X X X X X yXB.M.M.M.v.v.v.v.b.b.b.a.s.tXz.uXoXN.{. XjXzXkXnXlXHXJXHXHXlXXXc.{.iXoXJ.h.).-./.>.[ C b C _ | V b l C v l V x V ` l x X.( _.2.1.,.A.C.*.2.C.^.).^.:.V.Z.|./.V.!.+X+X+X+X!.:.+X-X=X!.l./.|.|.!.%.2.}.(.^.*.&.&.{ R Y T T Y U T U ! T { l.o.U ] ..[ ..O.g.$.c.{.a.X.B c x x x g P %.P K R R T U Q G XXl.g d 7 d 7 L ^ z f f a 3 a 3 7 j / ).l./ g.x o.] ^ h a d f z.] ..k.[ ^ ` z a X.z a a a d f c ^ ..UX ", +" UXX X X X X X X X X X X X % , X w , @ X X X X X X X X X X X X X X X X X X X 3 9 X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X U.B.B.B.M.v.s.a.v.z.n.s.z. Xm.rXiX XpXn.x.fXHXHXzXhXhXHXJXHXdXjXjXdXHXgX|.*X$.L c.{ | b z Z Z / C v h l l l l l $.B z h k ' V.C.:.,.-XC.1.=.V.$X/.V.}.$X^.$X+X2.2.2.V.~.V./.!.^./.|.vX=X*XgX#X^.%.C.$X/.V.&.! &.T U U U U T Y U { { c.j.#.-.] ..} .$...o.[ ..x.$.[ B B c z j h P K K T W R K Y K { {.R 7 f d a a d 3 C _ x v f d 3 3 7 d z b V / f.( l j j l f f c $.{ P o.c.L h d c.h.d B x f d _ f ) UX ", +" UXX X X X X X X X X X X X X X X , > & X X X X X X X X 4 X X X X X X X X 7 X @ % @ X X X X X X > X X @ X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X # U.B.B.v.v.v.v.s.a.a.s.b.N. X XuXpX.XK.h.m. XnXzXlXjXdXHXjXBX.XBXjXdXfXdX] ^ b c b v b z l l v l C V l l l l l x k l l h c / o.V.=.:.=.%.*.:.2.2.,.V.vX|.V.@X0.0.0.1.1.2.2.V.OX+X(.-X-X1XgX|./.!.^.$X*X:.,./.V.j.&.+.T G U T T { %.Z.c.U F +...] } [ ( ( ( $.] O.} B B c c c j g L Y J W T Q K K K F -.k d ^ j l a a < 7 L ..v l d d a f h h ^ ;.c c h f ) [ f.h x z _ x x b ..J.7 h O.X.f V .v a l f / UX ", +" UXX X X X @ X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X % X X X X X #.X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X @ O - X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X M rXB.M.M.M.v.v.s.a.s.s.v.v.z.N.uX X XtXx.O.sXnXHXzXzX&X-XtXk.dXvXCX*Xj.o.b _ ^ C C v l h v b z v v v l v l b k.$.x h l j x V $.@./.,.2.:.*.*.*.,.>.&.^.=.2.C.1.1.1.1.1.=.0.0.=.+X-X=X*X$X!./.V.2.,.:.&.*.&.%.! &.-.Q U U T T { &.^.Z.{ U P U o.[ ] P ] ] V U $.g.f.L c m c c z j k I F R ! R J H G G >.k ..c f d a a < a a h M ` z h a a a a a j h .` z ` M ( _ x B L j / ` P ;.h h j v h ^ O. .c a f z UX ", +" UXX X X X X X X X X X X X X X X X X X X X . X X X X X X X X X X X X X X X X X X X @ 7 X X X X X X X X X X X X X X X X X X X X X X X X X X X X O X X X X O - X X X . X X X X X X X X X X X X X X X X X X X X X X X X , X X X X X X X X X X X X X X X X X X X X X X > X | I.M.M.M.v.M.v.s.v.v.s.s.a.uXuXuXtX XdXrXo..XzXzXlXlXgXk.jXm.z.>.a.) ` ) b z v C z z l z v V b v l v v l h b {.c N x a.f.U ( $.P oX/.=.%.*.*.! ! &.*.:.1.1.1.=.1.1.8.1.1.0.0.=.~.@XcX=X+X-X+X!.1.*.! ! W ! W ! ! U Q K U G K +.l. Xl.&.c.F F { } [ ] Z ] ] Z } f.o.N v v c k j j g P P Y ! Y K Y i B g.k ) f g f d a a a a a a f n z j 7 < < < < d f f v [ O.x.P b B _ b b L j ..g a a < j _ ` b _ h a l UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X O X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X @ X X X X X X X X X X X X X X X X X X X X X s.I.M.M.M.v.M.s.s.s.a.a.a.z.n.K.tXrXJ.d.D.pXiXjXHXzXlXgXk.jX{.} ^ v v b b v z l l Z C C C C v C v C v l z z b o.^ ..B X. Xm.a.} o.] *X,.+.&.*.=.*.*.A.=.~ 1.~ *.0.0.=.1.w.0.=.0.2.~.!.`.-X@X@X0.0.*.W W T Q Y K Y K D F G R R -..XoX^.{ U G G $.} [ ] [ } .P / / m c c c b k j h g F T Y T R K Y F N #.g d a d d a a a a a a a f m z l f d d < a < a l ^ ' o.o.{.g.a.} c L L c m.( f z d a z _ ) V f a b UX ", +" UXX X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X : X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X M.I.M.M.M.v.v.s.s.s.a.b.M.n.K.x. X Xz.m.{.{.uXhXzXnXlXpXg.m.m.h.h._ ` n C C b v l b C B ) ) z _ n C V v b C V ` ] g.z. XK.m.f.} z.m.dX$X! =.1.1.=.~.2.1.=.*.&.*.2.1.1.w.2.0.1.1.1.1.0.,.2.)././.1.~ ! W W R K T K K U T T -.R &.^.,.%.{ l.R T $.} ] ] } $.] ] / ) ) ^ m j c z j f g P U Y ! T K G Y !.Y 7 d a a a < a a a a < a d j z h z f < d < a a a h c _ g ] Xk.f.v c _ L &X$.z l < a f v C V C f z UX ", +" UXX X X X X X X X X X X , X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X % X X X X X X X X X X X X X % X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X B.M.M.M.M.v.v.v.v.b.b.n.b.z.n. XF.N.g.m. XtXuXuXiXHXoX[ m.oX{.x.m._ _ b v z n v V _ _ l C b C a.) ` ^ B ( h.f.c..XuXN.D.iX Xx...x.uXhXuX:.&.1.0.2.1.*.*.~ &.*.1.=.1.~.0.0.0.0.1.1.2.1.,.2.2.~.!.1.~ W ! W W K Q %.T G U R +.U R +.R R j.{ U U #.o.] ` ] / ] / P P / ( / L c z j j j k I F R R Y F U >.N 7 f d < a d a a < a < a < < a a f f a a a a a a d f h f g x.x. Xx.g V ..m.f.V x f a l f f f b b v UX ", +" UXX X X X X X X X X @ X X X X @ X X X X X X X X X X X X X U X X X X X X X X X X X X X X X X X X X O X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X % X X X X X X X X X X X X & X X 3 X X X X X X X X X X , 7 X X X X X X X X X X X X X X X X X X X B.M.M.M.v.v.v.v.v.n.n.v. Xm.b.K.m.N.h.h.z.m.oXjXnXzXdXV f.x.z. XO.X.) C z z v b z...h.o.( ( z.X.` h.a.z.n.h.x.XXpXrXuXhXhXhXpX Xx.n. XuX{.2.2.).0.=.1.:.*.*.1.*.1.1.1.1.1.1.=.~ 0.1.1.2.2.2.2.0.*.&.~ W W K G Y R T F G Y R -.l.{ W { %.T G ] o.{ } [ [ ] } [ L L V L c c c k c c g g P G U G U U D V 7 d a a d a a d 3 < a a a < a < a h a < a < a a < 3 d b ` j B f.$.z.c c x h.L ..` z f b z l f v z ^ UX ", +" UXX X X X X X X X X X X @ X X X X X X X X X X X X X X X % I X X X X X X X @ X X X X X X X X X X X 4 9 X X X X X X @ > @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X & X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X # & U.B.M.M.M.v.v.v.v.v.z.v.s.b.m.h. Xz.m.b. X.X XuXhXlX{.$.O.c.[ z.b.z.x.C V ` k..X.X XtX Xx.d.F.x.b.h.x. X Xz.x.rXiXiXuXiXhXjXhXhXhXuXrX{.x.Z.`.'._.0.1.*.1.*.*.1.1.~ 1.1.1.0.1.1.1.1.0.=.1.=.=.1.=.~ %.W K K K Y R T K F U l.m.:.R -.Y U ' { } o...} } [ ] ( ] L B B B c c c k x x j j j B G U U N 0 g g a 3 a 3 < c f.` j f a < a a a f v a < a < f a a a < 7 h ^ c V k z.{ k.g.k X.} ..` / Z / v a b f b UX ", +" UXX X X X X X X X X X & X X X X X X X X X X X X X X X X 4 g X X X X X @ > X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X o B X O @ X X X X X X X X X X X X X & X X X X X X @ X X X X X X # # # < B.B.M.M.v.v.v.v.M.b.b.s.b.d.x.F.sXb.z.oXn.x.uXhXlX*XJ.` _ d.[ z.f.$.f.X.m.kXlXdXlXuXuX XrXK.x.m.m. XtXtX XK..XhXzXjXuXiXpXuXtXuXuXjXuX{.z.g.fX`._.0.1.:.2.~ %.~ 1.~ 1.1.3.0.0.1.0.0.8.1.=.=.=.~ ! *.W W T J R T K Q Y U { Y { T Y #.#.;.$.$.$.{ #...[ ] ] / L P L V B c c c c x x k k j j j k 8 7 g d d a d d P g.Z.Z.c.%XZ.h m z z ' k v ' ` l f d < a a a d d d x V _ h.J.{ hX|.L X.( ..L } ..z l V h h UX ", +" UXX X X X X X X X X X X X X X X X X X X X X X X X X X X > X X X X X X @ X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X . X X X X X X @ X X E X X X - X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 7 X X X > X X rXB.M.M.v.v.v.M.N.M.s.b.b.s.h.a.N. XuXK.tX{..X XjX{.XXd. .x.s.n.f.` z. XhXHXfXHXHXzXHXuX XrXK.m.c..X.X{.m.K. XjXhXjXuXuXiXuXiXtXuXjXzXzXhXm.jX$X/._.0.*.=.~ ~ *.1.<.1.1.1.1.0.1.0.0.0.0.=.1.1.~ ! ! R T R +.*.l.>.T &.J.{ F G U U #.g.x.;.@.o.o...o.X.] ] P / L P P V B m b x k c k j j g j g g g f a d a f c P j P F l.{ P l.*X-.f.[ X.` o.l.l.-.#.j j 7 f 7 d f d j v B } V P XXk.) x B V ` X./ [ X.v z UX ", +" UXX X X X X X X X X X X X X X X X X X X X % X X X : . X X X X X X X 7 X X X X X X X X X X X X X X X X . X X X X X X X X X X X X @ X X O X X X X X X X X X . X X X X X % X X X X X X X X X X X H X X X X X X X X X X X X X X X @ X X X X X X X X X X X % X X X X X X X U.B.B.M.M.M.v.M.b.n.b.n.s.a.a.z.) z. XuX..aXK.K.m.XXlXO. ._ O.h.m.z.oXjXlXlXgXXXlXzXCXHXhXuXhXuXK.} X.o.L } XlXzXuXF.uXuXhXlXhXhXlXhXzXnXnXCX+X`.`.0.=.1.1.=.*.1.1.1.1.1.w.0.w.8.1.1.1.0.1.&.! ! T Y K T T l.!.R K ).0.G N N G U >.k.;.f.f.o.o.;.....} ] / P V V B B B b k k c j c j f f 7 g g d f d d d j j h V j d o.x z k l.L d h 3 f { -.!.c.).dX*Xc.B h g v d g c x g 3 l ..k.L c d d b | b [ j z b UX ", +" UXX X X X X X X X X X X 4 X X X X X X X X X X X X @ . X X X X X X X X X @ = @ X X X X X X X X X X X X X X X X X X X X @ X X X X X X X X . X X X X X X X X X X X X X X . X X X X X % X X X X O 9 X X X X X # X X X X X > X X X X X X X X X X X X X X X X @ X X X X X > I.I.B.N.M.M.M.N.b.v.b.b.s.a.a.a.a._ z.{.` .Xm. XoXdXCX$.^ C ` _ h.g.hXlXzXzXnXBX*XlXbXhXD.oX{.k.X. .^ ( V [ uXuX Xm..XlXzXzXHXnXzXnXnXhXlXZXBX:.0.w._.1.1.=.=.1.1.~ 1.1.1.0.~.].~ 1.1.1.0.0.+X$X).,.Y Y ! Y R R Y K |.&.G F N F F { ;.;.@.;...o.f.o.} } ( / L B B B ( V x c c c c j j g h B ` B h x d d v L L ^ ) j d B 7 f v L 7 3 < a d 3 a a 7 f P Z.dXgXXXo.} B d j f f a 7 c $X..L 3 d h ) z z d f ) UX ", +" UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXPXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" " +}; diff --git a/resources/bitmaps/texture_browser.xpm b/resources/bitmaps/texture_browser.xpm new file mode 100644 index 0000000..20887ee --- /dev/null +++ b/resources/bitmaps/texture_browser.xpm @@ -0,0 +1,26 @@ +/* XPM */ +static char *texture_browser[] = { +/* columns rows colors chars-per-pixel */ +"16 16 4 1 ", +" c None", +". c black", +"X c #C0C0C0", +"o c white", +/* pixels */ +" ", +" .............. ", +" .ooooooXXXXXX. ", +" .ooooooXXXXXX. ", +" .ooooooXXXXXX. ", +" .ooooooXXXXXX. ", +" .ooooooXXXXXX. ", +" .ooooooXXXXXX. ", +" .XXXXXXoooooo. ", +" .XXXXXXoooooo. ", +" .XXXXXXoooooo. ", +" .XXXXXXoooooo. ", +" .XXXXXXoooooo. ", +" .XXXXXXoooooo. ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/texture_lock.xpm b/resources/bitmaps/texture_lock.xpm new file mode 100644 index 0000000..755e4f7 --- /dev/null +++ b/resources/bitmaps/texture_lock.xpm @@ -0,0 +1,27 @@ +/* XPM */ +static char *texture_lock[] = { +/* columns rows colors chars-per-pixel */ +"16 16 5 1 ", +" c None", +". c black", +"X c yellow", +"o c #C0C0C0", +"O c white", +/* pixels */ +" ", +" .............. ", +" .OOOOOOoooooo. ", +" .OOOO....oooo. ", +" .OOO......ooo. ", +" .OOO..Oo..ooo. ", +" .OOO..Oo..ooo. ", +" .OO........oo. ", +" .oo.XXXXXX.OO. ", +" .oo.XX..XX.OO. ", +" .oo.XX..XX.OO. ", +" .oo.XXXXXX.OO. ", +" .oo........OO. ", +" .ooooooOOOOOO. ", +" .............. ", +" " +}; diff --git a/resources/bitmaps/textures_popup.xpm b/resources/bitmaps/textures_popup.xpm new file mode 100644 index 0000000..a2181bf --- /dev/null +++ b/resources/bitmaps/textures_popup.xpm @@ -0,0 +1,27 @@ +/* XPM */ +static char *textures_popup[] = { +/* columns rows colors chars-per-pixel */ +"16 15 6 1 ", +" c None", +". c black", +"X c yellow", +"o c #888888", +"O c gray80", +"+ c white", +/* pixels */ +" oX .Ooo ", +" o+o.XOOOooo ", +"XoXoXOOOOOOOo. ", +"oX+ooo.OOOOOo. ", +"o+XXOOOOOOOoo. ", +"+o+OOoOOOOooo. ", +"Xo+O+O+OOoooo. ", +"o+.+O+OOooooo. ", +"OO+O+O+Oooooo. ", +"o+O+O+OOoooo. ", +"oO+O+O+Oooo. ", +" oooO+OOoo. ", +" oooOo. ...", +" o. .oo", +" .oo" +}; diff --git a/resources/bitmaps/undo.xpm b/resources/bitmaps/undo.xpm new file mode 100644 index 0000000..8c03cf3 --- /dev/null +++ b/resources/bitmaps/undo.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char *undo[] = { +/* columns rows colors chars-per-pixel */ +"16 16 2 1 ", +" c None", +". c navy", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" .... ", +" . .. . ", +" .. . . ", +" ... . ", +" .... . ", +" ..... . ", +" . ", +" ", +" ", +" ", +" " +}; diff --git a/resources/bitmaps/view_cameratoggle.xpm b/resources/bitmaps/view_cameratoggle.xpm new file mode 100644 index 0000000..b3016f5 --- /dev/null +++ b/resources/bitmaps/view_cameratoggle.xpm @@ -0,0 +1,29 @@ +/* XPM */ +static char *view_cameratoggle[] = { +/* columns rows colors chars-per-pixel */ +"16 15 8 1 ", +" c None", +". c black", +"X c yellow", +"o c #000088", +"O c blue", +"+ c #888888", +"@ c gray80", +"# c white", +/* pixels */ +"@OOOO+ ", +"OOOOOOO+ ", +"oOOOooOOOOOO+ ", +"ooOoOOOOOOOOoo ", +".oooOOOOOOOOOo+ ", +"+.ooOOOOOOOOO## ", +" .ooOoOOOOOX### ", +" +oooOoOOO#X##+ ", +" ooooOoO#XX## ", +" oooooO#XX##+ ", +" .oooo#XX##+ ", +" ..oOXXX##+ ", +" +.oO####+ ", +" ..####+ ", +" +##+ " +}; diff --git a/resources/bitmaps/view_cameraupdate.xpm b/resources/bitmaps/view_cameraupdate.xpm new file mode 100644 index 0000000..0f2b6de --- /dev/null +++ b/resources/bitmaps/view_cameraupdate.xpm @@ -0,0 +1,29 @@ +/* XPM */ +static char *view_cameraupdate[] = { +/* columns rows colors chars-per-pixel */ +"16 15 8 1 ", +" c None", +". c black", +"X c yellow", +"o c #000088", +"O c blue", +"+ c #888888", +"@ c gray80", +"# c white", +/* pixels */ +"@OO+ ", +"oOOoOOOO+ ", +"oOoOOOOOoo ", +"+ooOOOOOO# ", +" oooOOOO## ", +" oooOO#X## ", +" oooo#XX#+ ", +" ..OXX## ", +" +.O###+ . ", +" ## .+ ", +" .+ ", +" ....... ", +" ++.++++", +" .+ ", +" .+ " +}; diff --git a/resources/bitmaps/view_change.xpm b/resources/bitmaps/view_change.xpm new file mode 100644 index 0000000..b567406 --- /dev/null +++ b/resources/bitmaps/view_change.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char *view_change[] = { +/* columns rows colors chars-per-pixel */ +"16 16 2 1 ", +" c None", +". c black", +/* pixels */ +" ", +" . . ", +" . . ", +" . . . ", +" . . . ", +" . . . . ", +" . . . . ", +" . ", +" ..... . ", +" . . ", +" . . ", +" . . ", +" . ", +" . ", +" ..... ", +" " +}; diff --git a/resources/bitmaps/view_clipper.xpm b/resources/bitmaps/view_clipper.xpm new file mode 100644 index 0000000..2903a76 --- /dev/null +++ b/resources/bitmaps/view_clipper.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *view_clipper[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c blue", +/* pixels */ +" XXX", +" ............XXX", +" . XXX", +" . . ", +" . X . ", +" . X . ", +" . . ", +" . X . ", +" . X . ", +" . . ", +" . X . ", +" . X . ", +" . . ", +"XXX . ", +"XXX............ ", +"XXX " +}; diff --git a/resources/bitmaps/view_cubicclipping.xpm b/resources/bitmaps/view_cubicclipping.xpm new file mode 100644 index 0000000..c408215 --- /dev/null +++ b/resources/bitmaps/view_cubicclipping.xpm @@ -0,0 +1,25 @@ +/* XPM */ +static char *view_cubicclipping[] = { +/* columns rows colors chars-per-pixel */ +"16 16 3 1 ", +" c None", +". c black", +"X c #C0C0C0", +/* pixels */ +" ", +" . ", +" XXX. ", +" XXXXXX.X ", +" XXXXXXXXX.XX ", +" ..........XXX ", +" XXXXXXXX.XX ", +" XXXXXXX.XX ", +" XXXXXX.XX ", +" XXXXX.XX ", +" XXXX.X ", +" XXX.X ", +" XX.X ", +" X.X ", +" . ", +" " +}; diff --git a/resources/bitmaps/view_entity.xpm b/resources/bitmaps/view_entity.xpm new file mode 100644 index 0000000..d8c843e --- /dev/null +++ b/resources/bitmaps/view_entity.xpm @@ -0,0 +1,26 @@ +/* XPM */ +static char *view_entity[] = { +/* columns rows colors chars-per-pixel */ +"16 15 5 1 ", +" c None", +". c black", +"X c gray24", +"o c #888888", +"O c white", +/* pixels */ +" ", +" XXXXXXXXXXXX ", +" XOOOOOOOOOOXo ", +" XOOOOOOOOOOXo ", +" XO......O.OXo ", +" XOOOOOOOOOOXo ", +" XO......O.OXo ", +" XOOOOOOOOOOXo ", +" XO.O......OXo ", +" XOOOOOOOOOOXo ", +" XO..O.....OXo ", +" XOOOOOOOOOOXo ", +" XOOOOOOOOOOXo ", +" XXXXXXXXXXXXo ", +" oooooooooooo " +}; diff --git a/resources/bitmaps/white.xpm b/resources/bitmaps/white.xpm new file mode 100644 index 0000000..31b56ca --- /dev/null +++ b/resources/bitmaps/white.xpm @@ -0,0 +1,15 @@ +/* XPM */ +static char *white[] = { +/* columns rows colors chars-per-pixel */ +"8 8 1 1 ", +" c white", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" " +}; diff --git a/resources/bitmaps/window1.xpm b/resources/bitmaps/window1.xpm new file mode 100644 index 0000000..ae11c6f --- /dev/null +++ b/resources/bitmaps/window1.xpm @@ -0,0 +1,43 @@ +/* XPM */ +static char *window1[] = { +/* columns rows colors chars-per-pixel */ +"32 32 5 1 ", +" c #0000C0", +". c black", +"X c white", +"o c #C0C0C0", +"O c #C00000", +/* pixels */ +" ", +" . ", +" ", +".XXXXXXXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXOOOXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXOXXXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXOXXXXXXX.", +".XXXOXXXOXXXXXXXXXXoXXXOXXXXXXX.", +".XXXXOXOXXXXXXXXXXXoXXXXOOOXXXX.", +".XXXXXOXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXOXOXXXOXXXOXXXoXXXXXXXXXXX.", +".XXXOXXXOXXOXXXOXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXOXOXXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXXOXXXXXoooooooooooo.", +".XXXOOOOOXXXXOXXXXXoXXXXXXXXXXX.", +".XXXXXXOXXXXXOXXXXXoXXXXXXXXXXX.", +".XXXXXOXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXOXXXXXXXXXXXXXoXXXOOOOOXXX.", +".XXXOOOOOXXXXXXXXXXoXXXXXOXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXOXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXOXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXOXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXoXXXXXXXXXXX.", +".oooooooooooooooooooooooooooooo.", +".XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.", +"................................" +}; diff --git a/resources/bitmaps/window2.xpm b/resources/bitmaps/window2.xpm new file mode 100644 index 0000000..14a194e --- /dev/null +++ b/resources/bitmaps/window2.xpm @@ -0,0 +1,42 @@ +/* XPM */ +static char *window2[] = { +/* columns rows colors chars-per-pixel */ +"32 32 4 1 ", +" c #0000C0", +". c black", +"X c white", +"o c #C00000", +/* pixels */ +" ", +" . ", +" ", +".XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.", +".X XXX.", +".X XXX.", +".X.XXXXXXXXXXXXXXXXXXXXXXXX.XXX.", +".X.XXXXXXXXXXXXXXXXXX X.", +".X.XXoXXXoXXXXXXXXXXX X.", +".X.XXXoXoXXXXXXXXXXXX.XXXXXXX.X.", +".X.XXXXoXXXXXXXXXXXXX.XXXXXXX.X.", +".X.XXXoXoXXXoXXXoXXXX.XoooooX.X.", +".X.XXoXXXoXXoXXXoXXXX.XXXoXXX.X.", +".X.XXXXXXXXXXoXoXXXXX.XXXoXXX.X.", +".X.XXXXXXXXXXXoXXXXXX.XXXoXXX.X.", +".X.XXoooooXXXXoXXXXXX.XXXoXXX.X.", +".X.XXXXXoXXXXXoXXXXXX.XXXXXXX.X.", +".X.XXXXoXXXXXXXXXXXXX.XXXXXXX.X.", +".X.XXXoXXXXXXXXXXXXXX.XXXXXXX.X.", +".X.XXoooooXXXXXXXXXXX.........X.", +".X.XXXXXXXX XXXXXXX.XXX.", +".X.XXXXXXXX XXXXXXX.XXX.", +".X.XXXXXXXX.XXXXXXX.XXXXXXX.XXX.", +".X.XXXXXXXX.XXoooXX.XXXXXXX.XXX.", +".X.XXXXXXXX.XoXXXXX.XXXXXXX.XXX.", +".X.XXXXXXXX.XoXXXXX.XXXXXXX.XXX.", +".X.XXXXXXXX.XoXXXXX.XXXXXXX.XXX.", +".X..........XXoooXX.........XXX.", +".XXXXXXXXXX.XXXXXXX.XXXXXXXXXXX.", +".XXXXXXXXXX.........XXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.", +"................................" +}; diff --git a/resources/bitmaps/window3.xpm b/resources/bitmaps/window3.xpm new file mode 100644 index 0000000..c1513c3 --- /dev/null +++ b/resources/bitmaps/window3.xpm @@ -0,0 +1,43 @@ +/* XPM */ +static char *window3[] = { +/* columns rows colors chars-per-pixel */ +"32 32 5 1 ", +" c #0000C0", +". c black", +"X c white", +"o c #C0C0C0", +"O c #C00000", +/* pixels */ +" ", +" . ", +" ", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXOOOXXXXXXoXXOXXXOXXXXXXXX.", +".XXXXOXXXXXXXXXoXXXOXOXXXXXXXXX.", +".XXXXOXXXXXXXXXoXXXXOXXXOXXXOXX.", +".XXXXOXXXXXXXXXoXXXOXOXXOXXXOXX.", +".XXXXXOOOXXXXXXoXXOXXXOXXOXOXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXOXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXOXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXOXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".oooooooooooooooooooooooooooooo.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XOXXXOXXXXXXXXoXXOXXXOXXXXXXXX.", +".XOXXXOXXXXXXXXoXXXOXOXXXXXXXXX.", +".XXOXOXXOOOOOXXoXXXXOXXXOOOOOXX.", +".XXXOXXXXXXOXXXoXXXOXOXXXXXOXXX.", +".XXXOXXXXXOXXXXoXXOXXXOXXXOXXXX.", +".XXXOXXXXOXXXXXoXXXXXXXXXOXXXXX.", +".XXXXXXXOOOOOXXoXXXXXXXXOOOOOXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXoXXXXXXXXXXXXXXX.", +"................................" +}; diff --git a/resources/bitmaps/window4.xpm b/resources/bitmaps/window4.xpm new file mode 100644 index 0000000..3336ed5 --- /dev/null +++ b/resources/bitmaps/window4.xpm @@ -0,0 +1,43 @@ +/* XPM */ +static char *window4[] = { +/* columns rows colors chars-per-pixel */ +"32 32 5 1 ", +" c #0000C0", +". c black", +"X c white", +"o c #C0C0C0", +"O c #C00000", +/* pixels */ +" ", +" . ", +" ", +".XXXXXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXOOOXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXOXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXOXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXOXXXXXXXoXXXOXXXOXXXXXXXXXX.", +".XXXXOOOXXXXoXXXXOXOXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXXOXXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXOXOXXXOXXXOXXX.", +".XXXXXXXXXXXoXXXOXXXOXXOXXXOXXX.", +".XXXXXXXXXXXoXXXXXXXXXXXOXOXXXX.", +".ooooooooooooXXXXXXXXXXXXOXXXXX.", +".XXXXXXXXXXXoXXXOOOOOXXXXOXXXXX.", +".XXXXXXXXXXXoXXXXXXOXXXXXOXXXXX.", +".XXXXXXXXXXXoXXXXXOXXXXXXXXXXXX.", +".XXXOOOOOXXXoXXXXOXXXXXXXXXXXXX.", +".XXXXXOXXXXXoXXXOOOOOXXXXXXXXXX.", +".XXXXXOXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXOXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXOXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXoXXXXXXXXXXXXXXXXXX.", +".oooooooooooooooooooooooooooooo.", +".XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.", +".XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.", +"................................" +}; diff --git a/resources/games/platform.game b/resources/games/platform.game new file mode 100644 index 0000000..21f8712 --- /dev/null +++ b/resources/games/platform.game @@ -0,0 +1,27 @@ + + diff --git a/resources/gl/lighting_DBS_XY_Z_arbfp1.cg b/resources/gl/lighting_DBS_XY_Z_arbfp1.cg new file mode 100644 index 0000000..f535dbb --- /dev/null +++ b/resources/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/resources/gl/lighting_DBS_XY_Z_arbvp1.cg b/resources/gl/lighting_DBS_XY_Z_arbvp1.cg new file mode 100644 index 0000000..59a6fc7 --- /dev/null +++ b/resources/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/resources/gl/lighting_DBS_omni_fp.glp b/resources/gl/lighting_DBS_omni_fp.glp new file mode 100644 index 0000000..88dab8d --- /dev/null +++ b/resources/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/resources/gl/lighting_DBS_omni_fp.glsl b/resources/gl/lighting_DBS_omni_fp.glsl new file mode 100644 index 0000000..7f80fea --- /dev/null +++ b/resources/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/resources/gl/lighting_DBS_omni_vp.glp b/resources/gl/lighting_DBS_omni_vp.glp new file mode 100644 index 0000000..b4472d7 --- /dev/null +++ b/resources/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/resources/gl/lighting_DBS_omni_vp.glsl b/resources/gl/lighting_DBS_omni_vp.glsl new file mode 100644 index 0000000..6900b5a --- /dev/null +++ b/resources/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/resources/gl/utils.cg b/resources/gl/utils.cg new file mode 100644 index 0000000..63bfcb6 --- /dev/null +++ b/resources/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/resources/gl/zfill_arbfp1.cg b/resources/gl/zfill_arbfp1.cg new file mode 100644 index 0000000..c80189c --- /dev/null +++ b/resources/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/resources/gl/zfill_arbvp1.cg b/resources/gl/zfill_arbvp1.cg new file mode 100644 index 0000000..4ffc6e2 --- /dev/null +++ b/resources/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/resources/gl/zfill_fp.glp b/resources/gl/zfill_fp.glp new file mode 100644 index 0000000..5eb8b12 --- /dev/null +++ b/resources/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/resources/gl/zfill_fp.glsl b/resources/gl/zfill_fp.glsl new file mode 100644 index 0000000..537db67 --- /dev/null +++ b/resources/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/resources/gl/zfill_vp.glp b/resources/gl/zfill_vp.glp new file mode 100644 index 0000000..f6eda0c --- /dev/null +++ b/resources/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/resources/gl/zfill_vp.glsl b/resources/gl/zfill_vp.glsl new file mode 100644 index 0000000..4c81280 --- /dev/null +++ b/resources/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/resources/global.xlink b/resources/global.xlink new file mode 100644 index 0000000..e40a7f4 --- /dev/null +++ b/resources/global.xlink @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/platform.game/default_build_menu.xml b/resources/platform.game/default_build_menu.xml new file mode 100644 index 0000000..23a28bc --- /dev/null +++ b/resources/platform.game/default_build_menu.xml @@ -0,0 +1,26 @@ + + + +"[RadiantPath]vmap.[ExecutableType]" -v -connect [MonitorAddress] -game wastes -fs_basepath "[EnginePath]" -fs_game [GameName] + + + +[vmap] -custinfoparms -threads 4 -samplesize 8 "[MapFile]" +[vmap] -vis -v -fast "[MapFile]" + + + +[vmap] -custinfoparms -threads 4 -samplesize 8 "[MapFile]" +[vmap] -vis -v -fast "[MapFile]" +[vmap] -light -custinfoparms -v -samplesize 8 -fast -threads 4 -samples 4 -shade -shadeangle 60 -patchshadows "[MapFile]" + + + +[vmap] -custinfoparms -threads 4 -samplesize 8 "[MapFile]" +[vmap] -vis "[MapFile]" +[vmap] -light -custinfoparms -samplesize 8 -fast -threads 4 -samples 4 -shade -shadeangle 60 -patchshadows "[MapFile]" + + + + + diff --git a/resources/platform.game/platform/entities.def b/resources/platform.game/platform/entities.def new file mode 100644 index 0000000..6e96560 --- /dev/null +++ b/resources/platform.game/platform/entities.def @@ -0,0 +1,713 @@ +/*QUAKED item_ammo (1 0 0) (-8 -8 -8) (8 8 8) +Real beta entity, do not use, left for historical purposes +*/ +/*QUAKED item_health (1 0 0) (-8 -8 0) (8 8 16) +"targetname" Name + +Health pickup item for players that replenishes 25 health points and stops +any potential bleeding. + +model="models/game_objects/obj_health_kit.vvm" +*/ +/*QUAKED ammo_container (1 0 0) (-17 -17 0) (17 17 30) +"targetname" Name + +An ammunitions container, which fills up all weapons when used. + +model="models/ammo/ammo_crate.vvm" +*/ +/*QUAKED item_weapon (1 0 0) (-16 -16 0) (16 16 16) +"weapon" Weapon to give the player. +"delay" How big the magazine/clip is. Default is '-1'. + +Weapon pickup item. +When the magazine size is set to '-1', the weapon will be filled up. +*/ +/*QUAKED info_player_spectate (1 1 1) (-16 -16 -36) (16 16 36) + +Random spectator starting spot. +*/ +/*QUAKED info_player_start (1 0.5 0) (-16 -16 -36) (16 16 36) + +Singleplayer starting spot for players. +*/ +/*QUAKED info_player_deathmatch (1 1 0) (-16 -16 -36) (16 16 36) + +Random multiplayer starting spot for players. +*/ +/*QUAKED info_vehicle_start (0 1 1) (-50 -50 0) (-50 -50 70) + +Random vehicle starting spot for players in the Vehicular Carnage mode. +*/ +/*QUAKED info_player_team1 (1 0 0) (-16 -16 -36) (16 16 36) + +Random multiplayer starting spot for players of team 1. +*/ +/*QUAKED info_player_team2 (0 1 0) (-16 -16 -36) (16 16 36) + +Random multiplayer starting spot for players of team 2. +*/ +/*QUAKED sky_camera (1 0.3 1) (-8 -8 -8) (8 8 8) +"scale" Scale modifier. Default is '16'. + +Defines the position of a skyroom camera. +You want to put this into a dedicated room that contains a 3D skybox. +The scale modifier is more like a divider. You want to keep these +divisable by 2 to avoid any precision funky-ness. +*/ +/*QUAKED prop_dynamic (1 0 0) (-8 -8 -8) (8 8 8) +"model" Model file that will be displayed by the entity. +"modelscale" Scale modifier of the model. Default is '1'. +"angles" Sets the pitch, yaw and roll angles of the model. +"_cs" Toggles if the prop casts a shadow or not. + +Client-side decorative model entity. +*/ +/*QUAKED worldspawn (0 0 0) ? +"message" Level title. +"sounds" Music track to play on the level. +"_fog" Global fog defined in normalized distance, red, green blue, alpha + values. Use the fog console command to test before comitting. +"team1" Team 1 on this level. +"team2" Team 2 on this level. + +This is the main world entity definition. +Any structural brush belongs to it. + +Team numbers: + 0 = Killshots + 1 = Regulators + 2 = Ronin + 3 = Tribals + 4 = USMC + 5 = Vagrants +*/ +/*QUAKED env_glow (0 0.5 1) (-8 -8 -8) (8 8 8) +"shader" Material to use for the glare/glow effect. +"model" Sprite model to use for the glare/glow (idTech 2 only) +"scale" Scale multiplier. +"rendercolor" Material color override in RGB8. +"renderamt" Material alpha override in A8. + +Client-side glare/glow orb effect like the flares in Unreal. +*/ +/*QUAKED env_sound (0 1 0) (-8 -8 -8) (8 8 8) +"radius" Radius in units. +"roomtype" Roomtype value: + 0 = DEFAULT + 1 = PADDEDCELL + 2 = ROOM + 3 = BATHROOM + 4 = LIVINGROOM + 5 = STONEROOM + 6 = AUDITORIUM + 7 = CONCERTHALL + 8 = CAVE + 9 = ARENA + 10 = HANGAR + 11 = CARPETEDHALLWAY + 12 = HALLWAY + 13 = STONECORRIDOR + 14 = ALLEY + 15 = FOREST + 16 = CITY + 17 = MOUNTAINS + 18 = QUARRY + 19 = PLAIN + 20 = PARKINGLOT + 21 = SEWERPIPE + 22 = UNDERWATER + 23 = DRUGGED + 24 = DIZZY + 25 = PSYCHOTIC + 26 = CITYSTREETS + 27 = SUBWAY + 28 = MUSEUM + 29 = LIBRARY + 30 = UNDERPASS + 31 = ABANDONED + 32 = DUSTYROOM + 33 = CHAPEL + 34 = SMALLWATERROOM + +Client-side environmental reverb modifier. +This works only with the OpenAL sound backend. +*/ +/*QUAKED point_message (0.2 1 0.2) (-8 -8 -8) (8 8 8) +"message" The message to display. +"radius" The radius in which it will appear. + +Client-side overlay/message that is projected in relation to its position +in 3D space. +Used for zoo and test maps in which less interactive overlays are desired. +*/ +/*QUAKED env_cubemap (0 0 1) (-8 -8 -8) (8 8 8) +"scale" Texture dimension at which to render the cubemap. Default is '32'. + +Specifies a location for which a cubemap will be generated when the +dev_buildcubemaps console command is executed. +*/ +/*QUAKED env_fade (0 0 0) (-8 -8 -8) (8 8 8) EVF_FADEDROM EVF_MODULATE EVF_ONLYUSER +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"rendercolor" RGB8 Color of the fade effect. +"renderamt" A8 alpha value we'll hit at max. +"duration" Duration of the effect in seconds. +"holdtime" How long we'll hold on the max color/alpha. + +When triggered, creates a colored overlay that blinds all players, or just +the one who triggered it if EVF_ONLYUSER is set. +*/ +/*QUAKED env_shake (1 0.5 0) (-8 -8 -8) (8 8 8) EVS_GLOBAL +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"radius" Radius of the quake/shake effect. +"amplitude" Amplitude of the effect. +"duration" Duration of the effect in seconds. +"frequency" The frequency of the shake. + +Causes an earthquake/shaking effect when triggered. +Affects all clients (radius ignored) when EVS_GLOBAL is set. +*/ +/*QUAKED func_conveyor (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED func_lod (0 .5 .8) ? +"targetname" Name +"DisappearDist" = Unit distance at which stuff disappears +"solid" 0 = Be solid (wtf volvo), 1 = non-solid + +Disappearing entity for non VISable maps (ex. Valley) +*/ +/*QUAKED trigger_once (0 .5 .8) ? TO_MONSTERS TO_NOCLIENTS TO_PUSHABLES +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"delay" Delay until target is triggered. + +A trigger volume which works only once. + +None of the spawnflags are implemented yet. +*/ +/*QUAKED trigger_teleport (0 .5 .8) ? +"targetname" Name +"target" Which target to teleport to. + +Teleportation volume. Teleports anything it touches to the position of +any entity set as the "target". Works best with info_teleport_destination. +*/ +/*QUAKED func_wall (0 .5 .8) ? +"targetname" Name + +Brush that lets light to pass through it. +On idTech 2 based levels, it will change texture variants when triggered. +*/ +/*QUAKED path_track (1 0 1) (-8 -8 -8) (8 8 8) +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED trigger_changelevel (0 .5 .8) ? LC_NOINTERMISSION LC_USEONLY +"targetname" Name +"map" Next .bsp file name to transition to. +"landmark" Landmark name to target. +"changedelay" Time in seconds until the transition happens. + +When a Landmark is specified, you will have to position two info_landmark +entities across your two levels with the same name. They'll mark a translation +point for the coordinates in your levels. + +When LC_NOINTERMISSION is set, there'll be no stats screen at the end of the +level. + +When LC_USEONLY is set, it will not act as a trigger volume people can step in. +It'll have to be triggered by another entity. +*/ +/*QUAKED func_tracktrain (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +Moving platform following along path_* entities that's fully user controlled. +Very unfinished. +*/ +/*QUAKED env_message (1 0 0) (-8 -8 -8) (8 8 8) EMF_ONCE EMF_ALLPLAYERS +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"message" Message to send to players +"messagesound" PCM sample to play when triggered +"messagevolume" PCM sample volume +"messageattenuation" PCM sample attenuation + +Sends a message to either one or all players, depending on if EMF_ALLPLAYERS +is set. An optional sound effect can be supplied as well. +*/ +/*QUAKED item_food (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"angles" Sets the pitch, yaw and roll angles of the model. +"model" Model file that will be displayed by the entity. + +This is a food item that will give the user 1 health when touched. +*/ +/*QUAKED func_door (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED func_pushable (0 .5 .8) ? SF_TRIGGER SF_TOUCH SF_PRESSURE +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED trigger_relay (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +See trigger_once. +*/ +/*QUAKED game_text (1 0 0) (-8 -8 -8) (8 8 8) GTF_ALLPLAYERS +"targetname" Name +"x" Horizontal position of text. + (0 - 1.0 = left to right, -1 = center) +"y" Vertical position of text. + (0 - 1.0 = top to bottom, -1 = center) +"effect" Effect to apply to the text. + Valid values: + 0 = Fade In/Out + 1 = Credits + 2 = Scan Out +"color" The main colour in RGB8. +"color2" The highlight colour in RGB8. +"fadein" Time taken to fade in each character. +"fadeout" Time taken to fade out message. +"holdtime" Length of time to hold message on screen after fading in. +"fxtime" Time the highlight lags behind the leading edge of the text in + seconds. +"channel" Message channel to use. Meant for overriding messages. + +This entity displays a message of your choice on-screen. +Line breaks can be added with a \n character. + +If GTF_ALLPLAYERS is set, it'll display the message to not just the activator, +but all players on the level. +*/ +/*QUAKED multi_manager (0.5 1 0.7) (-8 -8 -8) (8 8 8) MM_MULTITHREADED +"targetname" Name + +Triggers a maximum of 16 user defined entities with additonal timers. +Add a target's name as an entity key, with the value set to the time in seconds +that'll pass before the entity will be triggered. + +If MM_MULTITHREADED is set, it'll allow the multi_manager to be triggered +again before it has finished triggering it's previous list of entities. +*/ +/*QUAKED env_spark (1 0 0) (-8 -8 -8) (8 8 8) x x x x x EVSPARK_TOGGLE EVSPARK_STARTON +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"angles" Sets the pitch, yaw and roll angles of the spark. +"MaxDelay" Delay between sparks when start-on (or toggle) is set + +Creates a series (or just one) spark effect with sound when triggered. +*/ +/*QUAKED multisource (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED trigger_cdaudio (0 .5 .8) ? +"targetname" Name +"health" Music track to play. + +Switches the background music track when triggered. +*/ +/*QUAKED trigger_multiple (0 .5 .8) ? TM_MONSTERS TM_NOCLIENTS TM_PUSHABLES +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"delay" Delay until target is triggered. +"wait" Time until this entity can trigger again + +A trigger volume which works more than once. + +None of the spawnflags are implemented yet. +*/ +/*QUAKED func_recharge (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED trigger_endsection (0 .5 .8) ? +"targetname" Name + +This trigger shuts down the server. +Useful for when a singleplayer game ends, as it takes you to the main menu. +*/ +/*QUAKED func_door_rotating (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED env_sprite (1 0 0) (-8 -8 -8) (8 8 8) ENVS_STARTON ENVS_PLAYONCE +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"angles" Sets the pitch, yaw and roll angles of the sprite. +"model" Path to the sprite in question. +"rendercolor" Color modifier of the sprite. +"renderamt" Alpha modifier of the sprite. +"rendermode" Render mode of the sprite. +"framerate" Rate between frames in seconds. +"scale" Scale modifier of the sprite. + +A sprite entity manager with fancy overrides. +Only used with an external sprite format, like SPR, SPRHL and SPR32. +*/ +/*QUAKED func_wall_toggle (0 .5 .8) ? FTW_STARTHIDDEN +"targetname" Name + +Brush that can be hidden and reappear when triggered. + +If FTW_STARTHIDDEN is set, it'll start hidden. +*/ +/*QUAKED env_explosion (1 0 0) (-8 -8 -8) (8 8 8) ENVEXPLO_NODAMAGE ENVEXPLO_REPEATABLE ENVEXPLO_NOBALL ENVEXPLO_NOSMOKE ENVEXPLO_NODECAL ENVEXPLO_NOSPARKS +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"iMagnitude" Magnitude of the explosion. + +When triggered, creates an explosion at its location. +*/ +/*QUAKED env_beverage (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"health" Amount of soda-cans that can be dispensed at maximum +"angles" Sets the pitch, yaw and roll angles of the soda + +Upon triggered, the entity will spawn item_food in its place in +the shape of a soda can. +*/ +/*QUAKED cycler_sprite (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"angles" Sets the pitch, yaw and roll angles of the model. +"model" Model file that will be displayed by the entity. + +Decorative, does nothing yet. +*/ +/*QUAKED light (1 0 0) (-8 -8 -8) (8 8 8) OFF_OR_LINEAR +"targetname" Name + +Infinitely small point of light illuminating the scene. + +idTech3 relevant keys: +"light" Light intensity value. Default is '300'. +"_color" Normalized RGB color value. Default is '1 1 1'. +"radius" Sets the light cone radius. Default is '64'. +"target" When set, targets an enity instead, becoming a spotlight. + +If OFF_OR_LINEAR is set, the light will be cast with a linear falloff instead +of inverse square. This is useful for bright lights that'll travel long +distances. + +idTech2 relevant keys: +"light" Defines the brightness of the light. +"style" Light style ID. 0-11 are defined, 12-32 are reserved for switched + lights. List of pre-defined styles: + 0 = Normal + 1 = Flicker A + 2 = Slow strong pulse + 3 = Candle A + 4 = Fast strobe + 5 = Gentle pulse + 6 = Flicker B + 7 = Candle B + 8 = Candle C + 9 = Slow strobe + 10 = Fluorescent flicker + 11 = Slow pulse, no black +"pattern" Custom light style pattern. Needs unique light style ID. + Patterns are defined with letters of the alphabet. + 'a' being dark. 'z' being fully lit. Can be a string of characters + that'll interpolate between at 10 FPS ingame. + +If OFF_OR_LINEAR is set, it starts off/disabled. +*/ +/*QUAKED trigger_auto (1 0 0) (-8 -8 -8) (8 8 8) TA_USEONCE +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"delay" Time in seconds until it triggers its target. + +Will automatically start working when the level has spawned. +If TA_USEONCE is set, it'll remove itself from the level permanently. +It will not survive round respawns, etc. +*/ +/*QUAKED func_train (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +Moving platform following along path_* entities. +Very unfinished. +*/ +/*QUAKED trigger_camera (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"angles" Sets the pitch, yaw and roll angles of the camera +"target" Which entity we're aiming at. Overrides angles. +"wait" How long to hold onto the target. + +Causes the activators first-person camera to switch to the view of this entity. +*/ +/*QUAKED ambient_generic (1 0 0) (-8 -8 -8) (8 8 8) AS_SRADIUS AS_MRADIUS AS_LRADIUS AS_SILENT AS_NOTTOGGLED +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"message" pcm file to play +"volume" 0.0 to 1.0 +"pitch" 0.0 to 2.0 + +Plays a PCM sample of whatever format the engine is configured to support. +If you want it to loop, you have to give the file itself a loop flag. + +TODO: Add a forced loop flag for non .wav samples? +*/ +/*QUAKED func_ladder (0 .5 .8) ? +"targetname" Name + +Ladder volume. Only useful in idTech 2 based levels. +Use brushes with the common/climb texture instead. +*/ +/*QUAKED func_illusionary (0 .5 .8) ? +"targetname" Name + +Brush that lets light to pass through it and is non-solid. +On idTech 2 based levels, it will change texture variants when triggered. +*/ +/*QUAKED trigger_push (0 .5 .8) ? TP_ONCE TP_STARTOFF +"targetname" Name +"speed" The speed (units per second) it'll apply to touchers. +"angles" Sets the direction of the push. + +Pushes anything in its volume into a direction of your choosing. + +If TP_ONCE is set, It'll only emit a single push once before disabling itself. +If TP_STARTOFF is set, it needs to be triggered first in order to function. +*/ +/*QUAKED info_null (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name + +Entity that does nothing and gets deleted upon level load. +This is used for map compiling stages only. +*/ +/*QUAKED info_notnull (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name + +It's like an info_null, it just does not got deleted upon level load. +Useful for any entities/triggers that need to target something. +*/ +/*QUAKED func_group (.5 .5 .5) ? +"_lightmapscale" Floating point value scaling the resolution of + lightmaps on brushes/patches in this entity. Default is '1.0'. +"_cs" Cast shadows. Can either be 1 or 0. Default is '1'. +"_rs" Receive shadows. Can either be 1 or 0. Default is '1'. + +These structural groups will simply result in static world geometry once the +map is compiled. It's not a true entity in itself and only needed at the +compile stage. +*/ +/*QUAKED monster_furniture (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"angles" Sets the pitch, yaw and roll angles of the model. +"model" Model file that will be displayed by the entity. + +Decorative, does nothing yet. +*/ +/*QUAKED func_button (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED func_dustmotes (0 .5 .8) ? +AHHHHHHHHHHHHHHHHHHH +*/ +/*QUAKED env_render (1 0 0) (-8 -8 -8) (8 8 8) SF_NORENDERFX SF_NORENDERAMT SF_NORENDERMODE SF_NORENDERCOLOR +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"rendermode" Render-Mode the target changes to +"renderamt" Render-Alpha the target changes to +"rendercolor" Render-Color the target changes to + +Changes the visual appearance of a target. +*/ +/*QUAKED monster_generic (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"angles" Sets the pitch, yaw and roll angles of the model. +"model" Model file that will be displayed by the entity. + +Decorative, does nothing yet. +*/ +/*QUAKED cycler (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"angles" Sets the pitch, yaw and roll angles of the model. +"sequence" Sets the animation the model should start in. +"model" Model file that will be displayed by the entity. + +Upon damage, the cycler will switch between all available animation +sequences. This is really for test-maps and showroom entities. +*/ +/*QUAKED env_shooter (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"angles" Sets the pitch, yaw and roll direction of the shooter. +"shootmodel" Model file to shoot. +"shootsounds" PCM sample to play whenever a piece shoots out. +"m_iGibs" Amount of models shot in total. +"m_flDelay" Delay before being able to be fired again. +"m_flVelocity" Speed of the models in units per second. +"m_flVariance" Delay between shots. +"m_flGibLife" Life of the individual model piece. +"scale" Scale modifier of the model pieces. + +Shoots model entities from its location. +*/ +/*QUAKED scripted_sequence (1 0 0) (-8 -8 -8) (8 8 8) x x SSFL_REPEATABLE SSFL_LEAVECORPSE x SSFL_NOINTERRUPT SSFL_OVERRIDEAI SSFL_NOSCRIPTMOVE +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"m_iszEntity" Entity targetname OR classname description to target +"m_iszPlay" After the monster has moved to the action point, play this animation +"m_iszIdle" Animation to play until the scripted_sequence is triggered +"m_flRadius" Search radius for m_targetMonster if a classname is specified +"m_flRepeat" Loop? Unused. +"m_fMoveTo" How we move to perform m_iActionAnim + +f_fMoveTo values: +0 = Don't move or turn +1 = Walk to the scripted_sequence +2 = Run to the scripted_sequence +3 = Unused/Not defined. Do not use this. +4 = Warp to the location of the scripted_sequence and perform the animation. +5 = Turn to the scripted_sequence's angle before performing the animation. + +Allow a monster to be selected and given an action to perform. +This is done in the form of olaying an animation. +*/ +/*QUAKED func_healthcharger (0 .5 .8) ? +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED func_breakable (0 .5 .8) ? SF_TRIGGER SF_TOUCH SF_PRESSURE +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"material" Material it's made of. +"delay" Delay in seconds of when it breaks under pressure. +"explodemagnitude" Strength of the explosion. + +Brush volume that can break into lots of little pieces. + +When SF_TOUCH is set, it'll break when an entity runs into it at high +velocities (damage is speed in units * 0.01). + +When SF_PRESSURE is set, it'll collapse once someone is standing on top of it. +At that point the "delay" key will decide after how many seconds the object +breaks. + +The strength of the explosion decides the radius (magnitude * 2.5) and the +maximum damage the explosion will do (you have to stand in the center for that). +*/ +/*QUAKED infodecal (1 0 0) (-8 -8 -8) (8 8 8) +"texture" Name of the texture inside decals.wad it projects onto a surface. + +This entity only works on BSP version 30 levels. +Projects a decals.wad texture onto the nearest surface. +It'll automatically figure out the surface based on distance. +The texture will be aligned along the surface texture normals. +*/ +/*QUAKED trigger_hurt (0 .5 .8) ? SF_HURT_ONCE SF_HURT_OFF x SF_HURT_NOPLAYERS SF_HURT_FIREONPLAYER SF_HURT_TOUCHPLAYER +"targetname" Name +"target" Target when triggered. +"delay" Delay until target is triggered. +"killtarget" Target to kill when triggered. +"dmg" Damage inflicted. + +Trigger volume that damages everything it touches. + +If SF_HURT_ONCE is set, it'll stop once it's been triggered the first time. +If SF_HURT_OFF is set, it needs to be triggered in order to work again. +If SF_HURT_NOPLAYERS is set, it will only NPCs. +If SF_HURT_TOUCHPLAYER is set, it'll only hurt players. +If SF_HURT_FIREONPLAYER is set, it'll only trigger a target if a player +activates it. +*/ +/*QUAKED path_corner (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. + +STUB! +*/ +/*QUAKED env_global (1 0 0) (-8 -8 -8) (8 8 8) GLOBAL_SETSPAWN +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"globalstate" The variable name in which we hold information in. +"initialstate" Initial mode: 0 = off, 1 = on, 2 = dead +"triggermode" Mode when triggered: 0 = off, 1 = on, 2 = dead + +Sets/kills a global variable that carries across levels. +Without GLOBAL_SETSPAWN set, it'll only modify existing values when +triggered. +*/ +/*QUAKED env_beam (1 0 0) (-8 -8 -8) (8 8 8) +"targetname" Name + +This entity is incomplete. Purely stub. +*/ +/*QUAKED trigger_transition (0 .5 .8) ? +"targetname" Name + +Currently unused. This is meant for defining level transition regions. +All entities touching this volume would carry across to the next level. +*/ +/*QUAKED func_rotating (0 .5 .8) ? FR_STARTON FR_REVERSE FR_ZAXIS FR_XAXIS FR_ACCDCC FR_FANPAIN FR_NOTSOLID FR_SMALLRADIUS FR_MRADIUS FR_LRADIUS +"targetname" Name +"target" Target when triggered. +"killtarget" Target to kill when triggered. +"speed" Speed in units per second. +"dmg" Damage applied to entity blocking its rotational path. + +Rotating brush object. Useful for fans, etc. +*/ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..245d551 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,120 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}") +include_directories(BEFORE common) + +set(Q3MAP_VERSION 2.5.17n) +find_package(Git REQUIRED) +execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if (GIT_VERSION) + set(Q3MAP_VERSION "${Q3MAP_VERSION}-git-${GIT_VERSION}") +endif () +add_definitions(-DQ3MAP_VERSION="${Q3MAP_VERSION}") + +find_package(GLIB REQUIRED) +include_directories(${GLIB_INCLUDE_DIRS}) + +find_package(JPEG REQUIRED) +include_directories(${JPEG_INCLUDE_DIR}) + +find_package(PNG REQUIRED) +include_directories(${PNG_INCLUDE_DIR}) + +find_package(LibXml2 REQUIRED) +include_directories(${LIBXML2_INCLUDE_DIR}) + +find_package(ZLIB REQUIRED) +include_directories(${ZLIB_INCLUDE_DIRS}) + +#find_package(Minizip REQUIRED) +#include_directories(${Minizip_INCLUDE_DIRS}) + +set(vmap_games + vmap/game_fte.h + ) + +radiant_tool(vmap + common/cmdlib.c common/cmdlib.h + common/imagelib.c common/imagelib.h + common/inout.c common/inout.h + common/jpeg.c + common/md4.c common/md4.h + common/mutex.c common/mutex.h + common/polylib.c common/polylib.h + common/polyset.h + common/qfiles.h + common/qthreads.h + common/scriplib.c common/scriplib.h + common/surfaceflags.h + common/threads.c + common/vfs.c common/vfs.h + vmap/brush.c + vmap/brush_primit.c + vmap/bsp.c + vmap/bsp_analyze.c + vmap/bsp_info.c + vmap/bsp_scale.c + vmap/bspfile_abstract.c + vmap/bspfile_ibsp.c + vmap/bspfile_rbsp.c + vmap/convert_ase.c + vmap/convert_bsp.c + vmap/convert_map.c + vmap/convert_obj.c + vmap/decals.c + vmap/exportents.c + vmap/facebsp.c + vmap/fixaas.c + vmap/fog.c + ${vmap_games} vmap/game__null.h + vmap/help.c + vmap/image.c + vmap/leakfile.c + vmap/light.c + vmap/light_bounce.c + vmap/light_trace.c + vmap/light_ydnar.c + vmap/lightmaps_ydnar.c + vmap/main.c + vmap/map.c + vmap/mesh.c + vmap/model.c + vmap/patch.c + vmap/path_init.c + vmap/portals.c + vmap/prtfile.c + vmap/vmap.h + vmap/shaders.c + vmap/surface.c + vmap/surface_extra.c + vmap/surface_foliage.c + vmap/surface_fur.c + vmap/surface_meta.c + vmap/tjunction.c + vmap/tree.c + vmap/vis.c + vmap/visflow.c + vmap/writebsp.c + ) + +target_link_libraries(vmap + ${GLIB_LIBRARIES} + ${JPEG_LIBRARIES} + ${PNG_LIBRARIES} + ${LIBXML2_LIBRARIES} + ${ZLIB_LIBRARIES} + ddslib + etclib + filematch + l_net + mathlib + minizip + picomodel + ) + +if (UNIX) + target_link_libraries(vmap pthread m) +endif () diff --git a/tools/common/aselib.c b/tools/common/aselib.c new file mode 100644 index 0000000..4961647 --- /dev/null +++ b/tools/common/aselib.c @@ -0,0 +1,894 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "aselib.h" +#include "globaldefs.h" +#include "inout.h" + +#include +#include +#include + +#define MAX_ASE_MATERIALS 32 +#define MAX_ASE_OBJECTS 64 +#define MAX_ASE_ANIMATIONS 32 +#define MAX_ASE_ANIMATION_FRAMES 512 + +#define VERBOSE( x ) { if ( ase.verbose ) { Sys_Printf x ; } } + +typedef struct +{ + float x, y, z; + float nx, ny, nz; + float s, t; +} aseVertex_t; + +typedef struct +{ + float s, t; +} aseTVertex_t; + +typedef int aseFace_t[3]; + +typedef struct +{ + int numFaces; + int numVertexes; + int numTVertexes; + + int timeValue; + + aseVertex_t *vertexes; + aseTVertex_t *tvertexes; + aseFace_t *faces, *tfaces; + + int currentFace, currentVertex; +} aseMesh_t; + +typedef struct +{ + int numFrames; + aseMesh_t frames[MAX_ASE_ANIMATION_FRAMES]; + + int currentFrame; +} aseMeshAnimation_t; + +typedef struct +{ + char name[128]; +} aseMaterial_t; + +/* +** contains the animate sequence of a single surface +** using a single material +*/ +typedef struct +{ + char name[128]; + + int materialRef; + int numAnimations; + + aseMeshAnimation_t anim; + +} aseGeomObject_t; + +typedef struct +{ + int numMaterials; + aseMaterial_t materials[MAX_ASE_MATERIALS]; + aseGeomObject_t objects[MAX_ASE_OBJECTS]; + + char *buffer; + char *curpos; + int len; + + int currentObject; + qboolean verbose; + qboolean grabAnims; + +} ase_t; + +static char s_token[1024]; +static ase_t ase; +static char gl_filename[1024]; + +static void ASE_Process( void ); +static void ASE_FreeGeomObject( int ndx ); + +#if GDEF_OS_LINUX || GDEF_OS_MACOS + +static char* strlwr( char* string ){ + char *cp; + for ( cp = string; *cp; ++cp ) + { + if ( 'A' <= *cp && *cp <= 'Z' ) { + *cp += 'a' - 'A'; + } + } + + return string; +} + +#endif + +/* +** ASE_Load +*/ +void ASE_Load( const char *filename, qboolean verbose, qboolean grabAnims ){ + FILE *fp = fopen( filename, "rb" ); + + if ( !fp ) { + Error( "File not found '%s'", filename ); + } + + memset( &ase, 0, sizeof( ase ) ); + + ase.verbose = verbose; + ase.grabAnims = grabAnims; + ase.len = Q_filelength( fp ); + + ase.curpos = ase.buffer = safe_malloc( ase.len ); + + Sys_Printf( "Processing '%s'\n", filename ); + + if ( fread( ase.buffer, ase.len, 1, fp ) != 1 ) { + fclose( fp ); + Error( "fread() != -1 for '%s'", filename ); + } + + fclose( fp ); + + strcpy( gl_filename, filename ); + + ASE_Process(); +} + +/* +** ASE_Free +*/ +void ASE_Free( void ){ + int i; + + for ( i = 0; i < ase.currentObject; i++ ) + { + ASE_FreeGeomObject( i ); + } +} + +/* +** ASE_GetNumSurfaces +*/ +int ASE_GetNumSurfaces( void ){ + return ase.currentObject; +} + +/* +** ASE_GetSurfaceName +*/ +const char *ASE_GetSurfaceName( int which ){ + aseGeomObject_t *pObject = &ase.objects[which]; + + if ( !pObject->anim.numFrames ) { + return 0; + } + + return pObject->name; +} + +/* +** ASE_GetSurfaceAnimation +** +** Returns an animation (sequence of polysets) +*/ +polyset_t *ASE_GetSurfaceAnimation( int which, int *pNumFrames, int skipFrameStart, int skipFrameEnd, int maxFrames ){ + aseGeomObject_t *pObject = &ase.objects[which]; + polyset_t *psets; + int numFramesInAnimation; + int numFramesToKeep; + int i, f; + + if ( !pObject->anim.numFrames ) { + return 0; + } + + if ( pObject->anim.numFrames > maxFrames && maxFrames != -1 ) { + numFramesInAnimation = maxFrames; + } + else + { + numFramesInAnimation = pObject->anim.numFrames; + if ( maxFrames != -1 ) { + Sys_FPrintf( SYS_WRN, "WARNING: ASE_GetSurfaceAnimation maxFrames > numFramesInAnimation\n" ); + } + } + + if ( skipFrameEnd != -1 ) { + numFramesToKeep = numFramesInAnimation - ( skipFrameEnd - skipFrameStart + 1 ); + } + else{ + numFramesToKeep = numFramesInAnimation; + } + + *pNumFrames = numFramesToKeep; + + psets = calloc( sizeof( polyset_t ) * numFramesToKeep, 1 ); + + for ( f = 0, i = 0; i < numFramesInAnimation; i++ ) + { + int t; + aseMesh_t *pMesh = &pObject->anim.frames[i]; + + if ( skipFrameStart != -1 ) { + if ( i >= skipFrameStart && i <= skipFrameEnd ) { + continue; + } + } + + strcpy( psets[f].name, pObject->name ); + strcpy( psets[f].materialname, ase.materials[pObject->materialRef].name ); + + psets[f].triangles = calloc( sizeof( triangle_t ) * pObject->anim.frames[i].numFaces, 1 ); + psets[f].numtriangles = pObject->anim.frames[i].numFaces; + + for ( t = 0; t < pObject->anim.frames[i].numFaces; t++ ) + { + int k; + + for ( k = 0; k < 3; k++ ) + { + psets[f].triangles[t].verts[k][0] = pMesh->vertexes[pMesh->faces[t][k]].x; + psets[f].triangles[t].verts[k][1] = pMesh->vertexes[pMesh->faces[t][k]].y; + psets[f].triangles[t].verts[k][2] = pMesh->vertexes[pMesh->faces[t][k]].z; + + if ( pMesh->tvertexes && pMesh->tfaces ) { + psets[f].triangles[t].texcoords[k][0] = pMesh->tvertexes[pMesh->tfaces[t][k]].s; + psets[f].triangles[t].texcoords[k][1] = pMesh->tvertexes[pMesh->tfaces[t][k]].t; + } + + } + } + + f++; + } + + return psets; +} + +static void ASE_FreeGeomObject( int ndx ){ + aseGeomObject_t *pObject; + int i; + + pObject = &ase.objects[ndx]; + + for ( i = 0; i < pObject->anim.numFrames; i++ ) + { + if ( pObject->anim.frames[i].vertexes ) { + free( pObject->anim.frames[i].vertexes ); + } + if ( pObject->anim.frames[i].tvertexes ) { + free( pObject->anim.frames[i].tvertexes ); + } + if ( pObject->anim.frames[i].faces ) { + free( pObject->anim.frames[i].faces ); + } + if ( pObject->anim.frames[i].tfaces ) { + free( pObject->anim.frames[i].tfaces ); + } + } + + memset( pObject, 0, sizeof( *pObject ) ); +} + +static aseMesh_t *ASE_GetCurrentMesh( void ){ + aseGeomObject_t *pObject; + + if ( ase.currentObject >= MAX_ASE_OBJECTS ) { + Error( "Too many GEOMOBJECTs" ); + return 0; // never called + } + + pObject = &ase.objects[ase.currentObject]; + + if ( pObject->anim.currentFrame >= MAX_ASE_ANIMATION_FRAMES ) { + Error( "Too many MESHes" ); + return 0; + } + + return &pObject->anim.frames[pObject->anim.currentFrame]; +} + +static int CharIsTokenDelimiter( int ch ){ + if ( ch <= 32 ) { + return 1; + } + return 0; +} + +static int ASE_GetToken( qboolean restOfLine ){ + int i = 0; + + if ( ase.buffer == 0 ) { + return 0; + } + + if ( ( ase.curpos - ase.buffer ) == ase.len ) { + return 0; + } + + // skip over crap + while ( ( ( ase.curpos - ase.buffer ) < ase.len ) && + ( *ase.curpos <= 32 ) ) + { + ase.curpos++; + } + + while ( ( ase.curpos - ase.buffer ) < ase.len ) + { + s_token[i] = *ase.curpos; + + ase.curpos++; + i++; + + if ( ( CharIsTokenDelimiter( s_token[i - 1] ) && !restOfLine ) || + ( ( s_token[i - 1] == '\n' ) || ( s_token[i - 1] == '\r' ) ) ) { + s_token[i - 1] = 0; + break; + } + } + + s_token[i] = 0; + + return 1; +} + +static void ASE_ParseBracedBlock( void ( *parser )( const char *token ) ){ + int indent = 0; + + while ( ASE_GetToken( qfalse ) ) + { + if ( !strcmp( s_token, "{" ) ) { + indent++; + } + else if ( !strcmp( s_token, "}" ) ) { + --indent; + if ( indent == 0 ) { + break; + } + else if ( indent < 0 ) { + Error( "Unexpected '}'" ); + } + } + else + { + if ( parser ) { + parser( s_token ); + } + } + } +} + +static void ASE_SkipEnclosingBraces( void ){ + int indent = 0; + + while ( ASE_GetToken( qfalse ) ) + { + if ( !strcmp( s_token, "{" ) ) { + indent++; + } + else if ( !strcmp( s_token, "}" ) ) { + indent--; + if ( indent == 0 ) { + break; + } + else if ( indent < 0 ) { + Error( "Unexpected '}'" ); + } + } + } +} + +static void ASE_SkipRestOfLine( void ){ + ASE_GetToken( qtrue ); +} + +static void ASE_KeyMAP_DIFFUSE( const char *token ){ + char bitmap[1024]; + char filename[1024]; + int i = 0; + + strcpy( filename, gl_filename ); + + if ( !strcmp( token, "*BITMAP" ) ) { + ASE_GetToken( qfalse ); + + // the purpose of this whole chunk of code below is to extract the relative path + // from a full path in the ASE + + strcpy( bitmap, s_token + 1 ); + if ( strchr( bitmap, '"' ) ) { + *strchr( bitmap, '"' ) = 0; + } + + /* convert backslash to slash */ + while ( bitmap[i] ) + { + if ( bitmap[i] == '\\' ) { + bitmap[i] = '/'; + } + i++; + } + + /* remove filename from path */ + for ( i = strlen( filename ); i > 0; i-- ) + { + if ( filename[i] == '/' ) { + filename[i] = '\0'; + break; + } + } + + /* replaces a relative path with a full path */ + if ( bitmap[0] == '.' && bitmap[1] == '.' && bitmap[2] == '/' ) { + while ( bitmap[0] == '.' && bitmap[1] == '.' && bitmap[2] == '/' ) + { + /* remove last item from path */ + for ( i = strlen( filename ); i > 0; i-- ) + { + if ( filename[i] == '/' ) { + filename[i] = '\0'; + break; + } + } + strcpy( bitmap, &bitmap[3] ); + } + strcat( filename, "/" ); + strcat( filename, bitmap ); + strcpy( bitmap, filename ); + } + + if ( strstr( bitmap, gamedir ) ) { + strcpy( ase.materials[ase.numMaterials].name, strstr( bitmap, gamedir ) + strlen( gamedir ) ); + Sys_Printf( "material name: \'%s\'\n", strstr( bitmap, gamedir ) + strlen( gamedir ) ); + } + else + { + sprintf( ase.materials[ase.numMaterials].name, "(not converted: '%s')", bitmap ); + Sys_FPrintf( SYS_WRN, "WARNING: illegal material name '%s'\n", bitmap ); + } + } + else + { + } +} + +static void ASE_KeyMATERIAL( const char *token ){ + if ( !strcmp( token, "*MAP_DIFFUSE" ) ) { + ASE_ParseBracedBlock( ASE_KeyMAP_DIFFUSE ); + } + else + { + } +} + +static void ASE_KeyMATERIAL_LIST( const char *token ){ + if ( !strcmp( token, "*MATERIAL_COUNT" ) ) { + ASE_GetToken( qfalse ); + VERBOSE( ( "..num materials: %s\n", s_token ) ); + if ( atoi( s_token ) > MAX_ASE_MATERIALS ) { + Error( "Too many materials!" ); + } + ase.numMaterials = 0; + } + else if ( !strcmp( token, "*MATERIAL" ) ) { + VERBOSE( ( "..material %d ", ase.numMaterials ) ); + ASE_ParseBracedBlock( ASE_KeyMATERIAL ); + ase.numMaterials++; + } +} + +static void ASE_KeyMESH_VERTEX_LIST( const char *token ){ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_VERTEX" ) ) { + ASE_GetToken( qfalse ); // skip number + + ASE_GetToken( qfalse ); + pMesh->vertexes[pMesh->currentVertex].y = atof( s_token ); + + ASE_GetToken( qfalse ); + pMesh->vertexes[pMesh->currentVertex].x = -atof( s_token ); + + ASE_GetToken( qfalse ); + pMesh->vertexes[pMesh->currentVertex].z = atof( s_token ); + + pMesh->currentVertex++; + + if ( pMesh->currentVertex > pMesh->numVertexes ) { + Error( "pMesh->currentVertex >= pMesh->numVertexes" ); + } + } + else + { + Error( "Unknown token '%s' while parsing MESH_VERTEX_LIST", token ); + } +} + +static void ASE_KeyMESH_FACE_LIST( const char *token ){ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_FACE" ) ) { + ASE_GetToken( qfalse ); // skip face number + + ASE_GetToken( qfalse ); // skip label + ASE_GetToken( qfalse ); // first vertex + pMesh->faces[pMesh->currentFace][0] = atoi( s_token ); + + ASE_GetToken( qfalse ); // skip label + ASE_GetToken( qfalse ); // second vertex + pMesh->faces[pMesh->currentFace][2] = atoi( s_token ); + + ASE_GetToken( qfalse ); // skip label + ASE_GetToken( qfalse ); // third vertex + pMesh->faces[pMesh->currentFace][1] = atoi( s_token ); + + ASE_GetToken( qtrue ); + +/* + if ( ( p = strstr( s_token, "*MESH_MTLID" ) ) != 0 ) + { + p += strlen( "*MESH_MTLID" ) + 1; + mtlID = atoi( p ); + } + else + { + Error( "No *MESH_MTLID found for face!" ); + } + */ + + pMesh->currentFace++; + } + else + { + Error( "Unknown token '%s' while parsing MESH_FACE_LIST", token ); + } +} + +static void ASE_KeyTFACE_LIST( const char *token ){ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TFACE" ) ) { + int a, b, c; + + ASE_GetToken( qfalse ); + + ASE_GetToken( qfalse ); + a = atoi( s_token ); + ASE_GetToken( qfalse ); + c = atoi( s_token ); + ASE_GetToken( qfalse ); + b = atoi( s_token ); + + pMesh->tfaces[pMesh->currentFace][0] = a; + pMesh->tfaces[pMesh->currentFace][1] = b; + pMesh->tfaces[pMesh->currentFace][2] = c; + + pMesh->currentFace++; + } + else + { + Error( "Unknown token '%s' in MESH_TFACE", token ); + } +} + +static void ASE_KeyMESH_TVERTLIST( const char *token ){ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TVERT" ) ) { + char u[80], v[80], w[80]; + + ASE_GetToken( qfalse ); + + ASE_GetToken( qfalse ); + strcpy( u, s_token ); + + ASE_GetToken( qfalse ); + strcpy( v, s_token ); + + ASE_GetToken( qfalse ); + strcpy( w, s_token ); + + pMesh->tvertexes[pMesh->currentVertex].s = atof( u ); + pMesh->tvertexes[pMesh->currentVertex].t = 1.0f - atof( v ); + + pMesh->currentVertex++; + + if ( pMesh->currentVertex > pMesh->numTVertexes ) { + Error( "pMesh->currentVertex > pMesh->numTVertexes" ); + } + } + else + { + Error( "Unknown token '%s' while parsing MESH_TVERTLIST", token ); + } +} + +static void ASE_KeyMESH( const char *token ){ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*TIMEVALUE" ) ) { + ASE_GetToken( qfalse ); + + pMesh->timeValue = atoi( s_token ); + VERBOSE( ( ".....timevalue: %d\n", pMesh->timeValue ) ); + } + else if ( !strcmp( token, "*MESH_NUMVERTEX" ) ) { + ASE_GetToken( qfalse ); + + pMesh->numVertexes = atoi( s_token ); + VERBOSE( ( ".....TIMEVALUE: %d\n", pMesh->timeValue ) ); + VERBOSE( ( ".....num vertexes: %d\n", pMesh->numVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMFACES" ) ) { + ASE_GetToken( qfalse ); + + pMesh->numFaces = atoi( s_token ); + VERBOSE( ( ".....num faces: %d\n", pMesh->numFaces ) ); + } + else if ( !strcmp( token, "*MESH_NUMTVFACES" ) ) { + ASE_GetToken( qfalse ); + + if ( atoi( s_token ) != pMesh->numFaces ) { + Error( "MESH_NUMTVFACES != MESH_NUMFACES" ); + } + } + else if ( !strcmp( token, "*MESH_NUMTVERTEX" ) ) { + ASE_GetToken( qfalse ); + + pMesh->numTVertexes = atoi( s_token ); + VERBOSE( ( ".....num tvertexes: %d\n", pMesh->numTVertexes ) ); + } + else if ( !strcmp( token, "*MESH_VERTEX_LIST" ) ) { + pMesh->vertexes = calloc( sizeof( aseVertex_t ) * pMesh->numVertexes, 1 ); + pMesh->currentVertex = 0; + VERBOSE( ( ".....parsing MESH_VERTEX_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_VERTEX_LIST ); + } + else if ( !strcmp( token, "*MESH_TVERTLIST" ) ) { + pMesh->currentVertex = 0; + pMesh->tvertexes = calloc( sizeof( aseTVertex_t ) * pMesh->numTVertexes, 1 ); + VERBOSE( ( ".....parsing MESH_TVERTLIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_TVERTLIST ); + } + else if ( !strcmp( token, "*MESH_FACE_LIST" ) ) { + pMesh->faces = calloc( sizeof( aseFace_t ) * pMesh->numFaces, 1 ); + pMesh->currentFace = 0; + VERBOSE( ( ".....parsing MESH_FACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_FACE_LIST ); + } + else if ( !strcmp( token, "*MESH_TFACELIST" ) ) { + pMesh->tfaces = calloc( sizeof( aseFace_t ) * pMesh->numFaces, 1 ); + pMesh->currentFace = 0; + VERBOSE( ( ".....parsing MESH_TFACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyTFACE_LIST ); + } + else if ( !strcmp( token, "*MESH_NORMALS" ) ) { + ASE_ParseBracedBlock( 0 ); + } +} + +static void ASE_KeyMESH_ANIMATION( const char *token ){ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + // loads a single animation frame + if ( !strcmp( token, "*MESH" ) ) { + VERBOSE( ( "...found MESH\n" ) ); + assert( pMesh->faces == 0 ); + assert( pMesh->vertexes == 0 ); + assert( pMesh->tvertexes == 0 ); + memset( pMesh, 0, sizeof( *pMesh ) ); + + ASE_ParseBracedBlock( ASE_KeyMESH ); + + if ( ++ase.objects[ase.currentObject].anim.currentFrame == MAX_ASE_ANIMATION_FRAMES ) { + Error( "Too many animation frames" ); + } + } + else + { + Error( "Unknown token '%s' while parsing MESH_ANIMATION", token ); + } +} + +static void ASE_KeyGEOMOBJECT( const char *token ){ + if ( !strcmp( token, "*NODE_NAME" ) ) { + char *name = ase.objects[ase.currentObject].name; + + ASE_GetToken( qtrue ); + VERBOSE( ( " %s\n", s_token ) ); + strcpy( ase.objects[ase.currentObject].name, s_token + 1 ); + if ( strchr( ase.objects[ase.currentObject].name, '"' ) ) { + *strchr( ase.objects[ase.currentObject].name, '"' ) = 0; + } + + if ( strstr( name, "tag" ) == name ) { + while ( strchr( name, '_' ) != strrchr( name, '_' ) ) + { + *strrchr( name, '_' ) = 0; + } + while ( strrchr( name, ' ' ) ) + { + *strrchr( name, ' ' ) = 0; + } + } + } + else if ( !strcmp( token, "*NODE_PARENT" ) ) { + ASE_SkipRestOfLine(); + } + // ignore unused data blocks + else if ( !strcmp( token, "*NODE_TM" ) || + !strcmp( token, "*TM_ANIMATION" ) ) { + ASE_ParseBracedBlock( 0 ); + } + // ignore regular meshes that aren't part of animation + else if ( !strcmp( token, "*MESH" ) && !ase.grabAnims ) { +/* + if ( strstr( ase.objects[ase.currentObject].name, "tag_" ) == ase.objects[ase.currentObject].name ) + { + s_forceStaticMesh = true; + ASE_ParseBracedBlock( ASE_KeyMESH ); + s_forceStaticMesh = false; + } + */ + ASE_ParseBracedBlock( ASE_KeyMESH ); + if ( ++ase.objects[ase.currentObject].anim.currentFrame == MAX_ASE_ANIMATION_FRAMES ) { + Error( "Too many animation frames" ); + } + ase.objects[ase.currentObject].anim.numFrames = ase.objects[ase.currentObject].anim.currentFrame; + ase.objects[ase.currentObject].numAnimations++; +/* + // ignore meshes that aren't part of animations if this object isn't a + // a tag + else + { + ASE_ParseBracedBlock( 0 ); + } + */ + } + // according to spec these are obsolete + else if ( !strcmp( token, "*MATERIAL_REF" ) ) { + ASE_GetToken( qfalse ); + + ase.objects[ase.currentObject].materialRef = atoi( s_token ); + } + // loads a sequence of animation frames + else if ( !strcmp( token, "*MESH_ANIMATION" ) ) { + if ( ase.grabAnims ) { + VERBOSE( ( "..found MESH_ANIMATION\n" ) ); + + if ( ase.objects[ase.currentObject].numAnimations ) { + Error( "Multiple MESH_ANIMATIONS within a single GEOM_OBJECT" ); + } + ASE_ParseBracedBlock( ASE_KeyMESH_ANIMATION ); + ase.objects[ase.currentObject].anim.numFrames = ase.objects[ase.currentObject].anim.currentFrame; + ase.objects[ase.currentObject].numAnimations++; + } + else + { + ASE_SkipEnclosingBraces(); + } + } + // skip unused info + else if ( !strcmp( token, "*PROP_MOTIONBLUR" ) || + !strcmp( token, "*PROP_CASTSHADOW" ) || + !strcmp( token, "*PROP_RECVSHADOW" ) ) { + ASE_SkipRestOfLine(); + } +} + +static void ConcatenateObjects( aseGeomObject_t *pObjA, aseGeomObject_t *pObjB ){ +} + +static void CollapseObjects( void ){ + int i; + int numObjects = ase.currentObject; + + for ( i = 0; i < numObjects; i++ ) + { + int j; + + // skip tags + if ( strstr( ase.objects[i].name, "tag" ) == ase.objects[i].name ) { + continue; + } + + if ( !ase.objects[i].numAnimations ) { + continue; + } + + for ( j = i + 1; j < numObjects; j++ ) + { + if ( strstr( ase.objects[j].name, "tag" ) == ase.objects[j].name ) { + continue; + } + if ( ase.objects[i].materialRef == ase.objects[j].materialRef ) { + if ( ase.objects[j].numAnimations ) { + ConcatenateObjects( &ase.objects[i], &ase.objects[j] ); + } + } + } + } +} + +/* +** ASE_Process +*/ +static void ASE_Process( void ){ + while ( ASE_GetToken( qfalse ) ) + { + if ( !strcmp( s_token, "*3DSMAX_ASCIIEXPORT" ) || + !strcmp( s_token, "*COMMENT" ) ) { + ASE_SkipRestOfLine(); + } + else if ( !strcmp( s_token, "*SCENE" ) ) { + ASE_SkipEnclosingBraces(); + } + else if ( !strcmp( s_token, "*MATERIAL_LIST" ) ) { + VERBOSE( ( "MATERIAL_LIST\n" ) ); + + ASE_ParseBracedBlock( ASE_KeyMATERIAL_LIST ); + } + else if ( !strcmp( s_token, "*GEOMOBJECT" ) ) { + VERBOSE( ( "GEOMOBJECT" ) ); + + ASE_ParseBracedBlock( ASE_KeyGEOMOBJECT ); + + if ( strstr( ase.objects[ase.currentObject].name, "Bip" ) || + strstr( ase.objects[ase.currentObject].name, "ignore_" ) ) { + ASE_FreeGeomObject( ase.currentObject ); + VERBOSE( ( "(discarding BIP/ignore object)\n" ) ); + } + else if ( ( strstr( ase.objects[ase.currentObject].name, "h_" ) != ase.objects[ase.currentObject].name ) && + ( strstr( ase.objects[ase.currentObject].name, "l_" ) != ase.objects[ase.currentObject].name ) && + ( strstr( ase.objects[ase.currentObject].name, "u_" ) != ase.objects[ase.currentObject].name ) && + ( strstr( ase.objects[ase.currentObject].name, "tag" ) != ase.objects[ase.currentObject].name ) && + ase.grabAnims ) { + VERBOSE( ( "(ignoring improperly labeled object '%s')\n", ase.objects[ase.currentObject].name ) ); + ASE_FreeGeomObject( ase.currentObject ); + } + else + { + if ( ++ase.currentObject == MAX_ASE_OBJECTS ) { + Error( "Too many GEOMOBJECTs" ); + } + } + } + else if ( s_token[0] ) { + Sys_Printf( "Unknown token '%s'\n", s_token ); + } + } + + if ( !ase.currentObject ) { + Error( "No animation data!" ); + } + + CollapseObjects(); +} diff --git a/tools/common/aselib.h b/tools/common/aselib.h new file mode 100644 index 0000000..f9a254d --- /dev/null +++ b/tools/common/aselib.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "../common/cmdlib.h" +#include "mathlib.h" +#include "polyset.h" + +void ASE_Load( const char *filename, qboolean verbose, qboolean meshanims ); +int ASE_GetNumSurfaces( void ); +polyset_t *ASE_GetSurfaceAnimation( int ndx, int *numFrames, int skipFrameStart, int skipFrameEnd, int maxFrames ); +const char *ASE_GetSurfaceName( int ndx ); +void ASE_Free( void ); diff --git a/tools/common/bspfile.c b/tools/common/bspfile.c new file mode 100644 index 0000000..4fc45bd --- /dev/null +++ b/tools/common/bspfile.c @@ -0,0 +1,706 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "bspfile.h" +#include "scriplib.h" + +void GetLeafNums( void ); + +//============================================================================= + +int bsp_version = Q3_BSP_VERSION; + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; + +int numShaders; +dshader_t dshaders[MAX_MAP_SHADERS]; + +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; + +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; + +int numleafsurfaces; +int dleafsurfaces[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +int dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +int numLightBytes; +byte *lightBytes; + +int numGridPoints; +byte *gridData; + +int numVisBytes; +byte visBytes[MAX_MAP_VISIBILITY]; + +int numDrawVerts = 0; +int numDrawVertsBuffer = 0; +drawVert_t *drawVerts = NULL; + +int numDrawIndexes; +int drawIndexes[MAX_MAP_DRAW_INDEXES]; + +int numDrawSurfaces; +int numDrawSurfacesBuffer = 0; +dsurface_t *drawSurfaces = NULL; + +int numFogs; +dfog_t dfogs[MAX_MAP_FOGS]; + +void SetLightBytes( int n ){ + if ( lightBytes != 0 ) { + free( lightBytes ); + } + + numLightBytes = n; + + if ( n == 0 ) { + return; + } + + lightBytes = safe_malloc_info( numLightBytes, "SetLightBytes" ); + + memset( lightBytes, 0, numLightBytes ); +} + +void SetGridPoints( int n ){ + if ( gridData != 0 ) { + free( gridData ); + } + + numGridPoints = n; + + if ( n == 0 ) { + return; + } + + gridData = safe_malloc_info( numGridPoints * 8, "SetGridPoints" ); + + memset( gridData, 0, numGridPoints * 8 ); +} + +void IncDrawVerts(){ + numDrawVerts++; + + if ( drawVerts == 0 ) { + numDrawVertsBuffer = MAX_MAP_DRAW_VERTS / 37; + + drawVerts = safe_malloc_info( sizeof( drawVert_t ) * numDrawVertsBuffer, "IncDrawVerts" ); + + } + else if ( numDrawVerts > numDrawVertsBuffer ) { + numDrawVertsBuffer *= 3; // multiply by 1.5 + numDrawVertsBuffer /= 2; + + if ( numDrawVertsBuffer > MAX_MAP_DRAW_VERTS ) { + numDrawVertsBuffer = MAX_MAP_DRAW_VERTS; + } + + drawVerts = realloc( drawVerts, sizeof( drawVert_t ) * numDrawVertsBuffer ); + + if ( !drawVerts ) { + Error( "realloc() failed (IncDrawVerts)" ); + } + } + + memset( drawVerts + ( numDrawVerts - 1 ), 0, sizeof( drawVert_t ) ); +} + +void SetDrawVerts( int n ){ + if ( drawVerts != 0 ) { + free( drawVerts ); + } + + numDrawVerts = n; + numDrawVertsBuffer = numDrawVerts; + + drawVerts = safe_malloc_info( sizeof( drawVert_t ) * numDrawVertsBuffer, "IncDrawVerts" ); + + memset( drawVerts, 0, n * sizeof( drawVert_t ) ); +} + +void SetDrawSurfacesBuffer(){ + if ( drawSurfaces != 0 ) { + free( drawSurfaces ); + } + + numDrawSurfacesBuffer = MAX_MAP_DRAW_SURFS; + + drawSurfaces = safe_malloc_info( sizeof( dsurface_t ) * numDrawSurfacesBuffer, "IncDrawSurfaces" ); + + memset( drawSurfaces, 0, MAX_MAP_DRAW_SURFS * sizeof( drawVert_t ) ); +} + +void SetDrawSurfaces( int n ){ + if ( drawSurfaces != 0 ) { + free( drawSurfaces ); + } + + numDrawSurfaces = n; + numDrawSurfacesBuffer = numDrawSurfaces; + + drawSurfaces = safe_malloc_info( sizeof( dsurface_t ) * numDrawSurfacesBuffer, "IncDrawSurfaces" ); + + memset( drawSurfaces, 0, n * sizeof( drawVert_t ) ); +} + +void BspFilesCleanup(){ + if ( drawVerts != 0 ) { + free( drawVerts ); + } + if ( drawSurfaces != 0 ) { + free( drawSurfaces ); + } + if ( lightBytes != 0 ) { + free( lightBytes ); + } + if ( gridData != 0 ) { + free( gridData ); + } +} + +//============================================================================= + +/* + ============= + SwapBlock + + If all values are 32 bits, this can be used to swap everything + ============= + */ +void SwapBlock( int *block, int sizeOfBlock ) { + int i; + + sizeOfBlock >>= 2; + for ( i = 0 ; i < sizeOfBlock ; i++ ) { + block[i] = LittleLong( block[i] ); + } +} + +/* + ============= + SwapBSPFile + + Byte swaps all data in a bsp file. + ============= + */ +void SwapBSPFile( void ) { + int i; + + // models + SwapBlock( (int *)dmodels, nummodels * sizeof( dmodels[0] ) ); + + // shaders (don't swap the name) + for ( i = 0 ; i < numShaders ; i++ ) { + dshaders[i].contentFlags = LittleLong( dshaders[i].contentFlags ); + dshaders[i].surfaceFlags = LittleLong( dshaders[i].surfaceFlags ); + } + + // planes + SwapBlock( (int *)dplanes, numplanes * sizeof( dplanes[0] ) ); + + // nodes + SwapBlock( (int *)dnodes, numnodes * sizeof( dnodes[0] ) ); + + // leafs + SwapBlock( (int *)dleafs, numleafs * sizeof( dleafs[0] ) ); + + // leaffaces + SwapBlock( (int *)dleafsurfaces, numleafsurfaces * sizeof( dleafsurfaces[0] ) ); + + // leafbrushes + SwapBlock( (int *)dleafbrushes, numleafbrushes * sizeof( dleafbrushes[0] ) ); + + // brushes + SwapBlock( (int *)dbrushes, numbrushes * sizeof( dbrushes[0] ) ); + + // brushsides + SwapBlock( (int *)dbrushsides, numbrushsides * sizeof( dbrushsides[0] ) ); + + // vis + ( (int *)&visBytes )[0] = LittleLong( ( (int *)&visBytes )[0] ); + ( (int *)&visBytes )[1] = LittleLong( ( (int *)&visBytes )[1] ); + + // drawverts (don't swap colors ) + for ( i = 0 ; i < numDrawVerts ; i++ ) { + drawVerts[i].lightmap[0] = LittleFloat( drawVerts[i].lightmap[0] ); + drawVerts[i].lightmap[1] = LittleFloat( drawVerts[i].lightmap[1] ); + drawVerts[i].st[0] = LittleFloat( drawVerts[i].st[0] ); + drawVerts[i].st[1] = LittleFloat( drawVerts[i].st[1] ); + drawVerts[i].xyz[0] = LittleFloat( drawVerts[i].xyz[0] ); + drawVerts[i].xyz[1] = LittleFloat( drawVerts[i].xyz[1] ); + drawVerts[i].xyz[2] = LittleFloat( drawVerts[i].xyz[2] ); + drawVerts[i].normal[0] = LittleFloat( drawVerts[i].normal[0] ); + drawVerts[i].normal[1] = LittleFloat( drawVerts[i].normal[1] ); + drawVerts[i].normal[2] = LittleFloat( drawVerts[i].normal[2] ); + } + + // drawindexes + SwapBlock( (int *)drawIndexes, numDrawIndexes * sizeof( drawIndexes[0] ) ); + + // drawsurfs + SwapBlock( (int *)drawSurfaces, numDrawSurfaces * sizeof( drawSurfaces[0] ) ); + + // fogs + for ( i = 0 ; i < numFogs ; i++ ) { + dfogs[i].brushNum = LittleLong( dfogs[i].brushNum ); + dfogs[i].visibleSide = LittleLong( dfogs[i].visibleSide ); + } +} + + + +/* + ============= + GetLumpElements + ============= + */ +int GetLumpElements( dheader_t *header, int lump, int size ) { + int length = header->lumps[lump].filelen; + + if ( length % size ) { + Error( "LoadBSPFile: odd lump size" ); + } + + return length / size; +} + +/* + ============= + CopyLump + ============= + */ +int CopyLump( dheader_t *header, int lump, void *dest, int size ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if ( length == 0 ) { + return 0; + } + + if ( length % size ) { + Error( "LoadBSPFile: odd lump size" ); + } + + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} + +/* + ============= + LoadBSPFile + ============= + */ +void LoadBSPFile( const char *filename ) { + dheader_t *header; + + // load the file header + LoadFile( filename, (void **)&header ); + + // swap the header + SwapBlock( (int *)header, sizeof( *header ) ); + + if ( header->ident != BSP_IDENT ) { + Error( "%s is not a IBSP file", filename ); + } + if ( header->version != bsp_version ) { + Error( "%s is version %i, not %i", filename, header->version, bsp_version ); + } + + numShaders = CopyLump( header, LUMP_SHADERS, dshaders, sizeof( dshader_t ) ); + nummodels = CopyLump( header, LUMP_MODELS, dmodels, sizeof( dmodel_t ) ); + numplanes = CopyLump( header, LUMP_PLANES, dplanes, sizeof( dplane_t ) ); + numleafs = CopyLump( header, LUMP_LEAFS, dleafs, sizeof( dleaf_t ) ); + numnodes = CopyLump( header, LUMP_NODES, dnodes, sizeof( dnode_t ) ); + numleafsurfaces = CopyLump( header, LUMP_LEAFSURFACES, dleafsurfaces, sizeof( dleafsurfaces[0] ) ); + numleafbrushes = CopyLump( header, LUMP_LEAFBRUSHES, dleafbrushes, sizeof( dleafbrushes[0] ) ); + numbrushes = CopyLump( header, LUMP_BRUSHES, dbrushes, sizeof( dbrush_t ) ); + numbrushsides = CopyLump( header, LUMP_BRUSHSIDES, dbrushsides, sizeof( dbrushside_t ) ); + numDrawVerts = GetLumpElements( header, LUMP_DRAWVERTS, sizeof( drawVert_t ) ); + SetDrawVerts( numDrawVerts ); + CopyLump( header, LUMP_DRAWVERTS, drawVerts, sizeof( drawVert_t ) ); + numDrawSurfaces = GetLumpElements( header, LUMP_SURFACES, sizeof( dsurface_t ) ); + SetDrawSurfaces( numDrawSurfaces ); + numDrawSurfaces = CopyLump( header, LUMP_SURFACES, drawSurfaces, sizeof( dsurface_t ) ); + numFogs = CopyLump( header, LUMP_FOGS, dfogs, sizeof( dfog_t ) ); + numDrawIndexes = CopyLump( header, LUMP_DRAWINDEXES, drawIndexes, sizeof( drawIndexes[0] ) ); + + numVisBytes = CopyLump( header, LUMP_VISIBILITY, visBytes, 1 ); + numLightBytes = GetLumpElements( header, LUMP_LIGHTMAPS, 1 ); + SetLightBytes( numLightBytes ); + CopyLump( header, LUMP_LIGHTMAPS, lightBytes, 1 ); + entdatasize = CopyLump( header, LUMP_ENTITIES, dentdata, 1 ); + + numGridPoints = GetLumpElements( header, LUMP_LIGHTGRID, 8 ); + SetGridPoints( numGridPoints ); + CopyLump( header, LUMP_LIGHTGRID, gridData, 8 ); + + + free( header ); // everything has been copied out + + // swap everything + SwapBSPFile(); +} + + +//============================================================================ + +/* + ============= + AddLump + ============= + */ +void AddLump( FILE *bspfile, dheader_t *header, int lumpnum, const void *data, int len ) { + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell( bspfile ) ); + lump->filelen = LittleLong( len ); + SafeWrite( bspfile, data, ( len + 3 ) & ~3 ); +} + +/* + ============= + WriteBSPFile + + Swaps the bsp file in place, so it should not be referenced again + ============= + */ +void WriteBSPFile( const char *filename ) { + dheader_t outheader, *header; + FILE *bspfile; + + header = &outheader; + memset( header, 0, sizeof( dheader_t ) ); + + SwapBSPFile(); + + header->ident = LittleLong( BSP_IDENT ); + header->version = LittleLong( bsp_version ); + + bspfile = SafeOpenWrite( filename ); + SafeWrite( bspfile, header, sizeof( dheader_t ) ); // overwritten later + + AddLump( bspfile, header, LUMP_SHADERS, dshaders, numShaders * sizeof( dshader_t ) ); + AddLump( bspfile, header, LUMP_PLANES, dplanes, numplanes * sizeof( dplane_t ) ); + AddLump( bspfile, header, LUMP_LEAFS, dleafs, numleafs * sizeof( dleaf_t ) ); + AddLump( bspfile, header, LUMP_NODES, dnodes, numnodes * sizeof( dnode_t ) ); + AddLump( bspfile, header, LUMP_BRUSHES, dbrushes, numbrushes * sizeof( dbrush_t ) ); + AddLump( bspfile, header, LUMP_BRUSHSIDES, dbrushsides, numbrushsides * sizeof( dbrushside_t ) ); + AddLump( bspfile, header, LUMP_LEAFSURFACES, dleafsurfaces, numleafsurfaces * sizeof( dleafsurfaces[0] ) ); + AddLump( bspfile, header, LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes * sizeof( dleafbrushes[0] ) ); + AddLump( bspfile, header, LUMP_MODELS, dmodels, nummodels * sizeof( dmodel_t ) ); + AddLump( bspfile, header, LUMP_DRAWVERTS, drawVerts, numDrawVerts * sizeof( drawVert_t ) ); + AddLump( bspfile, header, LUMP_SURFACES, drawSurfaces, numDrawSurfaces * sizeof( dsurface_t ) ); + AddLump( bspfile, header, LUMP_VISIBILITY, visBytes, numVisBytes ); + AddLump( bspfile, header, LUMP_LIGHTMAPS, lightBytes, numLightBytes ); + AddLump( bspfile, header, LUMP_LIGHTGRID, gridData, 8 * numGridPoints ); + AddLump( bspfile, header, LUMP_ENTITIES, dentdata, entdatasize ); + AddLump( bspfile, header, LUMP_FOGS, dfogs, numFogs * sizeof( dfog_t ) ); + AddLump( bspfile, header, LUMP_DRAWINDEXES, drawIndexes, numDrawIndexes * sizeof( drawIndexes[0] ) ); + + fseek( bspfile, 0, SEEK_SET ); + SafeWrite( bspfile, header, sizeof( dheader_t ) ); + fclose( bspfile ); +} + +//============================================================================ + +/* + ============= + PrintBSPFileSizes + + Dumps info about current file + ============= + */ +void PrintBSPFileSizes( void ) { + if ( !num_entities ) { + ParseEntities(); + } + + Sys_Printf( "%6i models %7i\n" + ,nummodels, (int)( nummodels * sizeof( dmodel_t ) ) ); + Sys_Printf( "%6i shaders %7i\n" + ,numShaders, (int)( numShaders * sizeof( dshader_t ) ) ); + Sys_Printf( "%6i brushes %7i\n" + ,numbrushes, (int)( numbrushes * sizeof( dbrush_t ) ) ); + Sys_Printf( "%6i brushsides %7i\n" + ,numbrushsides, (int)( numbrushsides * sizeof( dbrushside_t ) ) ); + Sys_Printf( "%6i fogs %7i\n" + ,numFogs, (int)( numFogs * sizeof( dfog_t ) ) ); + Sys_Printf( "%6i planes %7i\n" + ,numplanes, (int)( numplanes * sizeof( dplane_t ) ) ); + Sys_Printf( "%6i entdata %7i\n", num_entities, entdatasize ); + + Sys_Printf( "\n" ); + + Sys_Printf( "%6i nodes %7i\n" + ,numnodes, (int)( numnodes * sizeof( dnode_t ) ) ); + Sys_Printf( "%6i leafs %7i\n" + ,numleafs, (int)( numleafs * sizeof( dleaf_t ) ) ); + Sys_Printf( "%6i leafsurfaces %7i\n" + ,numleafsurfaces, (int)( numleafsurfaces * sizeof( dleafsurfaces[0] ) ) ); + Sys_Printf( "%6i leafbrushes %7i\n" + ,numleafbrushes, (int)( numleafbrushes * sizeof( dleafbrushes[0] ) ) ); + Sys_Printf( "%6i drawverts %7i\n" + ,numDrawVerts, (int)( numDrawVerts * sizeof( drawVerts[0] ) ) ); + Sys_Printf( "%6i drawindexes %7i\n" + ,numDrawIndexes, (int)( numDrawIndexes * sizeof( drawIndexes[0] ) ) ); + Sys_Printf( "%6i drawsurfaces %7i\n" + ,numDrawSurfaces, (int)( numDrawSurfaces * sizeof( drawSurfaces[0] ) ) ); + + Sys_Printf( "%6i lightmaps %7i\n" + ,numLightBytes / ( LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3 ), numLightBytes ); + Sys_Printf( " visibility %7i\n" + , numVisBytes ); +} + + +//============================================ + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing( char *e ) { + char *s; + + s = e + strlen( e ) - 1; + while ( s >= e && *s <= 32 ) + { + *s = 0; + s--; + } +} + +/* + ================= + ParseEpair + ================= + */ +epair_t *ParseEpair( void ) { + epair_t *e; + + e = safe_malloc( sizeof( epair_t ) ); + memset( e, 0, sizeof( epair_t ) ); + + if ( strlen( token ) >= MAX_KEY - 1 ) { + Error( "ParseEpar: token too long" ); + } + e->key = copystring( token ); + GetToken( qfalse ); + if ( strlen( token ) >= MAX_VALUE - 1 ) { + Error( "ParseEpar: token too long" ); + } + e->value = copystring( token ); + + // strip trailing spaces that sometimes get accidentally + // added in the editor + StripTrailing( e->key ); + StripTrailing( e->value ); + + return e; +} + + +/* + ================ + ParseEntity + ================ + */ +qboolean ParseEntity( void ) { + epair_t *e; + entity_t *mapent; + + if ( !GetToken( qtrue ) ) { + return qfalse; + } + + if ( strcmp( token, "{" ) ) { + Error( "ParseEntity: { not found" ); + } + if ( num_entities == MAX_MAP_ENTITIES ) { + Error( "num_entities == MAX_MAP_ENTITIES" ); + } + mapent = &entities[num_entities]; + num_entities++; + + do { + if ( !GetToken( qtrue ) ) { + Error( "ParseEntity: EOF without closing brace" ); + } + if ( !strcmp( token, "}" ) ) { + break; + } + e = ParseEpair(); + e->next = mapent->epairs; + mapent->epairs = e; + } while ( 1 ); + + return qtrue; +} + +/* + ================ + ParseEntities + + Parses the dentdata string into entities + ================ + */ +void ParseEntities( void ) { + num_entities = 0; + ParseFromMemory( dentdata, entdatasize ); + + while ( ParseEntity() ) { + } +} + + +/* + ================ + UnparseEntities + + Generates the dentdata string from all the entities + This allows the utilities to add or remove key/value pairs + to the data created by the map editor. + ================ + */ +void UnparseEntities( void ) { + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = dentdata; + end = buf; + *end = 0; + + for ( i = 0 ; i < num_entities ; i++ ) { + ep = entities[i].epairs; + if ( !ep ) { + continue; // ent got removed + } + + strcat( end,"{\n" ); + end += 2; + + for ( ep = entities[i].epairs ; ep ; ep = ep->next ) { + strcpy( key, ep->key ); + StripTrailing( key ); + strcpy( value, ep->value ); + StripTrailing( value ); + + sprintf( line, "\"%s\" \"%s\"\n", key, value ); + strcat( end, line ); + end += strlen( line ); + } + strcat( end,"}\n" ); + end += 2; + + if ( end > buf + MAX_MAP_ENTSTRING ) { + Error( "Entity text too long" ); + } + } + entdatasize = end - buf + 1; +} + +void PrintEntity( const entity_t *ent ) { + epair_t *ep; + + Sys_Printf( "------- entity %p -------\n", ent ); + for ( ep = ent->epairs ; ep ; ep = ep->next ) { + Sys_Printf( "%s = %s\n", ep->key, ep->value ); + } + +} + +void SetKeyValue( entity_t *ent, const char *key, const char *value ) { + epair_t *ep; + + for ( ep = ent->epairs ; ep ; ep = ep->next ) { + if ( !strcmp( ep->key, key ) ) { + free( ep->value ); + ep->value = copystring( value ); + return; + } + } + ep = safe_malloc( sizeof( *ep ) ); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring( key ); + ep->value = copystring( value ); +} + +const char *ValueForKey( const entity_t *ent, const char *key ) { + epair_t *ep; + + for ( ep = ent->epairs ; ep ; ep = ep->next ) { + if ( !strcmp( ep->key, key ) ) { + return ep->value; + } + } + return ""; +} + +vec_t FloatForKey( const entity_t *ent, const char *key ) { + const char *k; + + k = ValueForKey( ent, key ); + return atof( k ); +} + +void GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ) { + const char *k; + double v1, v2, v3; + + k = ValueForKey( ent, key ); + + // scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 ); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} diff --git a/tools/common/bspfile.h b/tools/common/bspfile.h new file mode 100644 index 0000000..a6f8679 --- /dev/null +++ b/tools/common/bspfile.h @@ -0,0 +1,120 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "qfiles.h" +#include "surfaceflags.h" + +extern int bsp_version; + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int numShaders; +extern dshader_t dshaders[MAX_MAP_MODELS]; + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; + +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern int numleafsurfaces; +extern int dleafsurfaces[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern int dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +extern int numbrushes; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +void SetLightBytes( int n ); +extern int numLightBytes; +extern byte *lightBytes; + +void SetGridPoints( int n ); +extern int numGridPoints; +extern byte *gridData; + +extern int numVisBytes; +extern byte visBytes[MAX_MAP_VISIBILITY]; + +void SetDrawVerts( int n ); +void IncDrawVerts(); +extern int numDrawVerts; +extern drawVert_t *drawVerts; + +extern int numDrawIndexes; +extern int drawIndexes[MAX_MAP_DRAW_INDEXES]; + +void SetDrawSurfaces( int n ); +void SetDrawSurfacesBuffer(); +extern int numDrawSurfaces; +extern dsurface_t *drawSurfaces; + +extern int numFogs; +extern dfog_t dfogs[MAX_MAP_FOGS]; + +void LoadBSPFile( const char *filename ); +void WriteBSPFile( const char *filename ); +void PrintBSPFileSizes( void ); + +//=============== + + +typedef struct epair_s { + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct { + vec3_t origin; + struct bspbrush_s *brushes; + struct parseMesh_s *patches; + int firstDrawSurf; + epair_t *epairs; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities( void ); +void UnparseEntities( void ); + +void SetKeyValue( entity_t *ent, const char *key, const char *value ); +const char *ValueForKey( const entity_t *ent, const char *key ); +// will return "" if not present + +vec_t FloatForKey( const entity_t *ent, const char *key ); +void GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ); + +epair_t *ParseEpair( void ); + +void PrintEntity( const entity_t *ent ); diff --git a/tools/common/cmdlib.c b/tools/common/cmdlib.c new file mode 100644 index 0000000..2d57aaa --- /dev/null +++ b/tools/common/cmdlib.c @@ -0,0 +1,1113 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// cmdlib.c +// TTimo 09/30/2000 +// from an intial copy of common/cmdlib.c +// stripped out the Sys_Printf Sys_Printf stuff + +// SPoG 05/27/2001 +// merging alpha branch into trunk +// replaced qprintf with Sys_Printf + +#include "cmdlib.h" +#include "globaldefs.h" +#include "mathlib.h" +#include "inout.h" +#include +#include + +#if GDEF_OS_WINDOWS +#include +#include +#endif + +#if GDEF_OS_LINUX || GDEF_OS_MACOS +#include +#endif + +#ifdef NeXT +#include +#endif + +#define BASEDIRNAME "quake" // assumed to have a 2 or 3 following +#define PATHSEPERATOR '/' + +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ){ + void *p; + + p = malloc( size ); + if ( !p ) { + Error( "safe_malloc failed on allocation of %i bytes", size ); + } + + return p; +} + +void *safe_malloc_info( size_t size, const char* info ){ + void *p; + + p = malloc( size ); + if ( !p ) { + Error( "%s: safe_malloc failed on allocation of %i bytes", info, size ); + } + + return p; +} +#endif + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +/* + =================== + ExpandWildcards + + Mimic unix command line expansion + =================== + */ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#if GDEF_OS_WINDOWS +#include "io.h" +void ExpandWildcards( int *argc, char ***argv ){ + struct _finddata_t fileinfo; + int handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for ( i = 0 ; i < *argc ; i++ ) + { + path = ( *argv )[i]; + if ( path[0] == '-' + || ( !strstr( path, "*" ) && !strstr( path, "?" ) ) ) { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst( path, &fileinfo ); + if ( handle == -1 ) { + return; + } + + ExtractFilePath( path, filebase ); + + do + { + sprintf( filename, "%s%s", filebase, fileinfo.name ); + ex_argv[ex_argc++] = copystring( filename ); + } while ( _findnext( handle, &fileinfo ) != -1 ); + + _findclose( handle ); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards( int *argc, char ***argv ){ +} +#endif + +/* + + qdir will hold the path up to the quake directory, including the slash + + f:\quake\ + /raid/quake/ + + gamedir will hold qdir + the game directory (id1, id2, etc) + + */ + +char qdir[1024]; +char gamedir[1024]; +char writedir[1024]; + +void SetQdirFromPath( const char *path ){ + char temp[1024]; + const char *c; + const char *sep; + int len, count; + + if ( !( path[0] == '/' || path[0] == '\\' || path[1] == ':' ) ) { // path is partial + Q_getwd( temp ); + strcat( temp, path ); + path = temp; + } + + // search for "quake2" in path + + len = strlen( BASEDIRNAME ); + for ( c = path + strlen( path ) - 1 ; c != path ; c-- ) + { + int i; + + if ( !Q_strncasecmp( c, BASEDIRNAME, len ) ) { + // + //strncpy (qdir, path, c+len+2-path); + // the +2 assumes a 2 or 3 following quake which is not the + // case with a retail install + // so we need to add up how much to the next separator + sep = c + len; + count = 1; + while ( *sep && *sep != '/' && *sep != '\\' ) + { + sep++; + count++; + } + strncpy( qdir, path, c + len + count - path ); + Sys_Printf( "qdir: %s\n", qdir ); + for ( i = 0; i < (int) strlen( qdir ); i++ ) + { + if ( qdir[i] == '\\' ) { + qdir[i] = '/'; + } + } + + c += len + count; + while ( *c ) + { + if ( *c == '/' || *c == '\\' ) { + strncpy( gamedir, path, c + 1 - path ); + + for ( i = 0; i < (int) strlen( gamedir ); i++ ) + { + if ( gamedir[i] == '\\' ) { + gamedir[i] = '/'; + } + } + + Sys_Printf( "gamedir: %s\n", gamedir ); + + if ( !writedir[0] ) { + strcpy( writedir, gamedir ); + } + else if ( writedir[strlen( writedir ) - 1] != '/' ) { + writedir[strlen( writedir )] = '/'; + writedir[strlen( writedir ) + 1] = 0; + } + + return; + } + c++; + } + Error( "No gamedir in %s", path ); + return; + } + } + Error( "SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path ); +} + +char *ExpandArg( const char *path ){ + static char full[1024]; + + if ( path[0] != '/' && path[0] != '\\' && path[1] != ':' ) { + Q_getwd( full ); + strcat( full, path ); + } + else{ + strcpy( full, path ); + } + return full; +} + +char *ExpandPath( const char *path ){ + static char full[1024]; + if ( path[0] == '/' || path[0] == '\\' || path[1] == ':' ) { + strcpy( full, path ); + return full; + } + sprintf( full, "%s%s", qdir, path ); + return full; +} + +char *copystring( const char *s ){ + char *b; + b = safe_malloc( strlen( s ) + 1 ); + strcpy( b, s ); + return b; +} + + + +/* + ================ + I_FloatTime + ================ + */ +double I_FloatTime( void ){ + time_t t; + + time( &t ); + + return t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday( &tp, &tzp ); + + if ( !secbase ) { + secbase = tp.tv_sec; + return tp.tv_usec / 1000000.0; + } + + return ( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0; +#endif +} + +void Q_getwd( char *out ){ + int i = 0; + +#if GDEF_OS_WINDOWS + _getcwd( out, 256 ); + strcat( out, "\\" ); +#else + // Gef: Changed from getwd() to getcwd() to avoid potential buffer overflow + if ( !getcwd( out, 256 ) ) { + *out = 0; + } + strcat( out, "/" ); +#endif + while ( out[i] != 0 ) + { + if ( out[i] == '\\' ) { + out[i] = '/'; + } + i++; + } +} + + +void Q_mkdir( const char *path ){ + char parentbuf[256]; + const char *p = NULL; + int retry = 2; + while ( retry-- ) + { +#if GDEF_OS_WINDOWS + const char *q = NULL; + if ( _mkdir( path ) != -1 ) { + return; + } + if ( errno == ENOENT ) { + p = strrchr( path, '/' ); + q = strrchr( path, '\\' ); + if ( q && ( !p || q < p ) ) { + p = q; + } + } +#else + if ( mkdir( path, 0777 ) != -1 ) { + return; + } + if ( errno == ENOENT ) { + p = strrchr( path, '/' ); + } +#endif + if ( p ) { + strncpy( parentbuf, path, sizeof( parentbuf ) ); + if ( (int) ( p - path ) < (int) sizeof( parentbuf ) ) { + parentbuf[p - path] = 0; + Sys_Printf( "mkdir: %s: creating parent %s first\n", path, parentbuf ); + Q_mkdir( parentbuf ); + continue; + } + } + break; + } + if ( errno != EEXIST ) { + Error( "mkdir %s: %s",path, strerror( errno ) ); + } +} + +/* + ============ + FileTime + + returns -1 if not present + ============ + */ +int FileTime( const char *path ){ + struct stat buf; + + if ( stat( path,&buf ) == -1 ) { + return -1; + } + + return buf.st_mtime; +} + + + +/* + ============== + COM_Parse + + Parse a token out of a string + ============== + */ +char *COM_Parse( char *data ){ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if ( !data ) { + return NULL; + } + +// skip whitespace +skipwhite: + while ( ( c = *data ) <= ' ' ) + { + if ( c == 0 ) { + com_eof = qtrue; + return NULL; // end of file; + } + data++; + } + +// skip // comments + if ( c == '/' && data[1] == '/' ) { + while ( *data && *data != '\n' ) + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if ( c == '\"' ) { + data++; + do + { + c = *data++; + if ( c == '\"' ) { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while ( 1 ); + } + +// parse single characters + if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) { + com_token[len] = c; + len++; + com_token[len] = 0; + return data + 1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) { + break; + } + } while ( c > 32 ); + + com_token[len] = 0; + return data; +} + +int Q_strncasecmp( const char *s1, const char *s2, int n ){ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + + } + if ( c1 != c2 ) { + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ( 'a' - 'A' ); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ( 'a' - 'A' ); + } + if ( c1 != c2 ) { + return -1; // strings not equal + } + } + } while ( c1 ); + + return 0; // strings are equal +} + +int Q_stricmp( const char *s1, const char *s2 ){ + return Q_strncasecmp( s1, s2, 99999 ); +} + +// NOTE TTimo when switching to Multithread DLL (Release/Debug) in the config +// started getting warnings about that function, prolly a duplicate with the runtime function +// maybe we still need to have it in linux builds +/* + char *strupr (char *start) + { + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; + } + */ + +char *strlower( char *start ){ + char *in; + in = start; + while ( *in ) + { + *in = tolower( *in ); + in++; + } + return start; +} + + +/* + ============================================================================= + + MISC FUNCTIONS + + ============================================================================= + */ + + +/* + ================= + CheckParm + + Checks for the given parameter in the program's command line arguments + Returns the argument number (1 to argc-1) or 0 if not present + ================= + */ +int CheckParm( const char *check ){ + int i; + + for ( i = 1; i < myargc; i++ ) + { + if ( !Q_stricmp( check, myargv[i] ) ) { + return i; + } + } + + return 0; +} + + + +/* + ================ + Q_filelength + ================ + */ +int Q_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; +} + + +FILE *SafeOpenWrite( const char *filename ){ + FILE *f; + + f = fopen( filename, "wb" ); + + if ( !f ) { + Error( "Error opening %s: %s",filename,strerror( errno ) ); + } + + return f; +} + +FILE *SafeOpenRead( const char *filename ){ + FILE *f; + + f = fopen( filename, "rb" ); + + if ( !f ) { + Error( "Error opening %s: %s",filename,strerror( errno ) ); + } + + return f; +} + + +void SafeRead( FILE *f, void *buffer, int count ){ + if ( fread( buffer, 1, count, f ) != (size_t)count ) { + Error( "File read failure" ); + } +} + + +void SafeWrite( FILE *f, const void *buffer, int count ){ + if ( fwrite( buffer, 1, count, f ) != (size_t)count ) { + Error( "File write failure" ); + } +} + + +/* + ============== + FileExists + ============== + */ +qboolean FileExists( const char *filename ){ + FILE *f; + + f = fopen( filename, "r" ); + if ( !f ) { + return qfalse; + } + fclose( f ); + return qtrue; +} + +/* + ============== + LoadFile + ============== + */ +int LoadFile( const char *filename, void **bufferptr ){ + FILE *f; + int length; + void *buffer; + + f = SafeOpenRead( filename ); + length = Q_filelength( f ); + buffer = safe_malloc( length + 1 ); + ( (char *)buffer )[length] = 0; + SafeRead( f, buffer, length ); + fclose( f ); + + *bufferptr = buffer; + return length; +} + + +/* + ============== + LoadFileBlock + - + rounds up memory allocation to 4K boundry + - + ============== + */ +int LoadFileBlock( const char *filename, void **bufferptr ){ + FILE *f; + int length, nBlock, nAllocSize; + void *buffer; + + f = SafeOpenRead( filename ); + length = Q_filelength( f ); + nAllocSize = length; + nBlock = nAllocSize % MEM_BLOCKSIZE; + if ( nBlock > 0 ) { + nAllocSize += MEM_BLOCKSIZE - nBlock; + } + buffer = safe_malloc( nAllocSize + 1 ); + memset( buffer, 0, nAllocSize + 1 ); + SafeRead( f, buffer, length ); + fclose( f ); + + *bufferptr = buffer; + return length; +} + + +/* + ============== + TryLoadFile + + Allows failure + ============== + */ +int TryLoadFile( const char *filename, void **bufferptr ){ + FILE *f; + int length; + void *buffer; + + *bufferptr = NULL; + + f = fopen( filename, "rb" ); + if ( !f ) { + return -1; + } + length = Q_filelength( f ); + buffer = safe_malloc( length + 1 ); + ( (char *)buffer )[length] = 0; + SafeRead( f, buffer, length ); + fclose( f ); + + *bufferptr = buffer; + return length; +} + + +/* + ============== + SaveFile + ============== + */ +void SaveFile( const char *filename, const void *buffer, int count ){ + FILE *f; + + f = SafeOpenWrite( filename ); + SafeWrite( f, buffer, count ); + fclose( f ); +} + + + +void DefaultExtension( char *path, const char *extension ){ + char *src; +// +// if path doesnt have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen( path ) - 1; + + while ( *src != '/' && *src != '\\' && src != path ) + { + if ( *src == '.' ) { + return; // it has an extension + } + src--; + } + + strcat( path, extension ); +} + + +void DefaultPath( char *path, const char *basepath ){ + char temp[128]; + + if ( path[ 0 ] == '/' || path[ 0 ] == '\\' ) { + return; // absolute path location + } + strcpy( temp,path ); + strcpy( path,basepath ); + strcat( path,temp ); +} + + +void StripFilename( char *path ){ + int length; + + length = strlen( path ) - 1; + while ( length > 0 && path[length] != '/' && path[ length ] != '\\' ) + length--; + path[length] = 0; +} + +void StripExtension( char *path ){ + int length; + + length = strlen( path ) - 1; + while ( length > 0 && path[length] != '.' ) + { + length--; + if ( path[length] == '/' || path[ length ] == '\\' ) { + return; // no extension + } + } + if ( length ) { + path[length] = 0; + } +} + + +/* + ==================== + Extract file parts + ==================== + */ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash +void ExtractFilePath( const char *path, char *dest ){ + const char *src; + + src = path + strlen( path ) - 1; + +// +// back up until a \ or the start +// + while ( src != path && *( src - 1 ) != '\\' && *( src - 1 ) != '/' ) + src--; + + memcpy( dest, path, src - path ); + dest[src - path] = 0; +} + +void ExtractFileBase( const char *path, char *dest ){ + const char *src; + + src = path + strlen( path ) - 1; + +// +// back up until a \ or the start +// + while ( src != path && *( src - 1 ) != '/' && *( src - 1 ) != '\\' ) + src--; + + while ( *src && *src != '.' ) + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension( const char *path, char *dest ){ + const char *src; + + src = path + strlen( path ) - 1; + +// +// back up until a . or the start +// + while ( src != path && *( src - 1 ) != '.' ) + src--; + if ( src == path ) { + *dest = 0; // no extension + return; + } + + strcpy( dest,src ); +} + + +/* + ============== + ParseNum / ParseHex + ============== + */ +int ParseHex( const char *hex ){ + const char *str; + int num; + + num = 0; + str = hex; + + while ( *str ) + { + num <<= 4; + if ( *str >= '0' && *str <= '9' ) { + num += *str - '0'; + } + else if ( *str >= 'a' && *str <= 'f' ) { + num += 10 + *str - 'a'; + } + else if ( *str >= 'A' && *str <= 'F' ) { + num += 10 + *str - 'A'; + } + else{ + Error( "Bad hex number: %s",hex ); + } + str++; + } + + return num; +} + + +int ParseNum( const char *str ){ + if ( str[0] == '$' ) { + return ParseHex( str + 1 ); + } + if ( str[0] == '0' && str[1] == 'x' ) { + return ParseHex( str + 2 ); + } + return atol( str ); +} + + + +/* + ============================================================================ + + BYTE ORDER FUNCTIONS + + ============================================================================ + */ + +#if GDEF_ARCH_ENDIAN_BIG + +short LittleShort( short l ){ + byte b1,b2; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + + return ( b1 << 8 ) + b2; +} + +short BigShort( short l ){ + return l; +} + + +int LittleLong( int l ){ + byte b1,b2,b3,b4; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + b3 = ( l >> 16 ) & 255; + b4 = ( l >> 24 ) & 255; + + return ( (int)b1 << 24 ) + ( (int)b2 << 16 ) + ( (int)b3 << 8 ) + b4; +} + +int BigLong( int l ){ + return l; +} + + +float LittleFloat( float l ){ + union {byte b[4]; float f; } in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat( float l ){ + return l; +} + + +#else + + +short BigShort( short l ){ + byte b1,b2; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + + return ( b1 << 8 ) + b2; +} + +short LittleShort( short l ){ + return l; +} + + +int BigLong( int l ){ + byte b1,b2,b3,b4; + + b1 = l & 255; + b2 = ( l >> 8 ) & 255; + b3 = ( l >> 16 ) & 255; + b4 = ( l >> 24 ) & 255; + + return ( (int)b1 << 24 ) + ( (int)b2 << 16 ) + ( (int)b3 << 8 ) + b4; +} + +int LittleLong( int l ){ + return l; +} + +float BigFloat( float l ){ + union {byte b[4]; float f; } in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat( float l ){ + return l; +} + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init( unsigned short *crcvalue ){ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte( unsigned short *crcvalue, byte data ){ + *crcvalue = ( *crcvalue << 8 ) ^ crctable[( *crcvalue >> 8 ) ^ data]; +} + +unsigned short CRC_Value( unsigned short crcvalue ){ + return crcvalue ^ CRC_XOR_VALUE; +} +//============================================================================= + +/* + ============ + CreatePath + ============ + */ +void CreatePath( const char *path ){ + const char *ofs; + char c; + char dir[1024]; + +#if GDEF_OS_WINDOWS + int olddrive = -1; + + if ( path[1] == ':' ) { + olddrive = _getdrive(); + _chdrive( toupper( path[0] ) - 'A' + 1 ); + } +#endif + + if ( path[1] == ':' ) { + path += 2; + } + + for ( ofs = path + 1 ; *ofs ; ofs++ ) + { + c = *ofs; + if ( c == '/' || c == '\\' ) { // create the directory + memcpy( dir, path, ofs - path ); + dir[ ofs - path ] = 0; + Q_mkdir( dir ); + } + } + +#if GDEF_OS_WINDOWS + if ( olddrive != -1 ) { + _chdrive( olddrive ); + } +#endif +} + + +/* + ============ + QCopyFile + + Used to archive source files + ============ + */ +void QCopyFile( const char *from, const char *to ){ + void *buffer; + int length; + + length = LoadFile( from, &buffer ); + CreatePath( to ); + SaveFile( to, buffer, length ); + free( buffer ); +} + +void Sys_Sleep( int n ){ +#if GDEF_OS_WINDOWS + Sleep( n ); +#endif +#if GDEF_OS_LINUX || GDEF_OS_MACOS + usleep( n * 1000 ); +#endif +} diff --git a/tools/common/cmdlib.h b/tools/common/cmdlib.h new file mode 100644 index 0000000..b487004 --- /dev/null +++ b/tools/common/cmdlib.h @@ -0,0 +1,163 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// cmdlib.h + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#include "bytebool.h" +#include "globaldefs.h" + +#if GDEF_COMPILER_MSVC +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float + +#pragma check_stack(off) + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if GDEF_COMPILER_MSVC + +#pragma intrinsic( memset, memcpy ) + +#endif + + +#ifdef PATH_MAX +#define MAX_OS_PATH PATH_MAX +#else +#define MAX_OS_PATH 4096 +#endif +#define MEM_BLOCKSIZE 4096 + +// the dec offsetof macro doesnt work very well... +#define myoffsetof( type,identifier ) ( (size_t)& ( (type *)0 )->identifier ) + +#define SAFE_MALLOC +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ); +void *safe_malloc_info( size_t size, const char* info ); +#else +#define safe_malloc( a ) malloc( a ) +#endif /* SAFE_MALLOC */ + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +char *strlower( char *in ); +int Q_strncasecmp( const char *s1, const char *s2, int n ); +int Q_stricmp( const char *s1, const char *s2 ); +void Q_getwd( char *out ); + +int Q_filelength( FILE *f ); +int FileTime( const char *path ); + +void Q_mkdir( const char *path ); + +extern char qdir[1024]; +extern char gamedir[1024]; +extern char writedir[1024]; +extern char *moddirparam; +void SetQdirFromPath( const char *path ); +char *ExpandArg( const char *path ); // from cmd line +char *ExpandPath( const char *path ); // from scripts +void ExpandWildcards( int *argc, char ***argv ); + + +double I_FloatTime( void ); + +void Error( const char *error, ... ) GDEF_ATTRIBUTE_NORETURN; +int CheckParm( const char *check ); + +FILE *SafeOpenWrite( const char *filename ); +FILE *SafeOpenRead( const char *filename ); +void SafeRead( FILE *f, void *buffer, int count ); +void SafeWrite( FILE *f, const void *buffer, int count ); + +int LoadFile( const char *filename, void **bufferptr ); +int LoadFileBlock( const char *filename, void **bufferptr ); +int TryLoadFile( const char *filename, void **bufferptr ); +void SaveFile( const char *filename, const void *buffer, int count ); +qboolean FileExists( const char *filename ); + +void DefaultExtension( char *path, const char *extension ); +void DefaultPath( char *path, const char *basepath ); +void StripFilename( char *path ); +void StripExtension( char *path ); + +void ExtractFilePath( const char *path, char *dest ); +void ExtractFileBase( const char *path, char *dest ); +void ExtractFileExtension( const char *path, char *dest ); + +int ParseNum( const char *str ); + +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +float BigFloat( float l ); +float LittleFloat( float l ); + + +char *COM_Parse( char *data ); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring( const char *s ); + + +void CRC_Init( unsigned short *crcvalue ); +void CRC_ProcessByte( unsigned short *crcvalue, byte data ); +unsigned short CRC_Value( unsigned short crcvalue ); + +void CreatePath( const char *path ); +void QCopyFile( const char *from, const char *to ); + +extern qboolean archive; +extern char archivedir[1024]; + +// sleep for the given amount of milliseconds +void Sys_Sleep( int n ); + +// for compression routines +typedef struct +{ + void *data; + int count, width, height; +} cblock_t; + + +#endif diff --git a/tools/common/imagelib.c b/tools/common/imagelib.c new file mode 100644 index 0000000..5bc5ff7 --- /dev/null +++ b/tools/common/imagelib.c @@ -0,0 +1,1534 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// imagelib.c + +#include "inout.h" +#include "cmdlib.h" +#include "etclib.h" +#include "imagelib.h" +#include "vfs.h" + +int fgetLittleShort( FILE *f ){ + byte b1, b2; + + b1 = fgetc( f ); + b2 = fgetc( f ); + + return (short)( b1 + b2 * 256 ); +} + +int fgetLittleLong( FILE *f ){ + byte b1, b2, b3, b4; + + b1 = fgetc( f ); + b2 = fgetc( f ); + b3 = fgetc( f ); + b4 = fgetc( f ); + + return b1 + ( b2 << 8 ) + ( b3 << 16 ) + ( b4 << 24 ); +} + +int bufLittleShort( byte *buf, int len, int *pos ){ + byte b1, b2; + + if ( ( len - *pos ) < 2 ) { + Error( "Unexpected buffer end" ); + } + + b1 = buf[*pos]; *pos += 1; + b2 = buf[*pos]; *pos += 1; + + return (short)( b1 + b2 * 256 ); +} + +int bufLittleLong( byte *buf, int len, int *pos ){ + byte b1, b2, b3, b4; + + if ( ( len - *pos ) < 4 ) { + Error( "Unexpected buffer end" ); + } + + b1 = buf[*pos]; *pos += 1; + b2 = buf[*pos]; *pos += 1; + b3 = buf[*pos]; *pos += 1; + b4 = buf[*pos]; *pos += 1; + + return b1 + ( b2 << 8 ) + ( b3 << 16 ) + ( b4 << 24 ); +} + + +/* + ============================================================================ + + LBM STUFF + + ============================================================================ + */ + + +typedef unsigned char UBYTE; +//conflicts with windows typedef short WORD; +typedef unsigned short UWORD; +typedef long LONG; + +typedef enum +{ + ms_none, + ms_mask, + ms_transcolor, + ms_lasso +} mask_t; + +typedef enum +{ + cm_none, + cm_rle1 +} compress_t; + +typedef struct +{ + UWORD w,h; + short x,y; + UBYTE nPlanes; + UBYTE masking; + UBYTE compression; + UBYTE pad1; + UWORD transparentColor; + UBYTE xAspect,yAspect; + short pageWidth,pageHeight; +} bmhd_t; + +extern bmhd_t bmhd; // will be in native byte order + + + +#define FORMID ( 'F' + ( 'O' << 8 ) + ( (int)'R' << 16 ) + ( (int)'M' << 24 ) ) +#define ILBMID ( 'I' + ( 'L' << 8 ) + ( (int)'B' << 16 ) + ( (int)'M' << 24 ) ) +#define PBMID ( 'P' + ( 'B' << 8 ) + ( (int)'M' << 16 ) + ( (int)' ' << 24 ) ) +#define BMHDID ( 'B' + ( 'M' << 8 ) + ( (int)'H' << 16 ) + ( (int)'D' << 24 ) ) +#define BODYID ( 'B' + ( 'O' << 8 ) + ( (int)'D' << 16 ) + ( (int)'Y' << 24 ) ) +#define CMAPID ( 'C' + ( 'M' << 8 ) + ( (int)'A' << 16 ) + ( (int)'P' << 24 ) ) + + +bmhd_t bmhd; + +int Align( int l ){ + if ( l & 1 ) { + return l + 1; + } + return l; +} + + + +/* + ================ + LBMRLEdecompress + + Source must be evenly aligned! + ================ + */ +byte *LBMRLEDecompress( byte *source,byte *unpacked, int bpwidth ){ + int count; + byte b,rept; + + count = 0; + + do + { + rept = *source++; + + if ( rept > 0x80 ) { + rept = ( rept ^ 0xff ) + 2; + b = *source++; + memset( unpacked,b,rept ); + unpacked += rept; + } + else if ( rept < 0x80 ) { + rept++; + memcpy( unpacked,source,rept ); + unpacked += rept; + source += rept; + } + else{ + rept = 0; // rept of 0x80 is NOP + + } + count += rept; + + } while ( count < bpwidth ); + + if ( count > bpwidth ) { + Error( "Decompression exceeded width!\n" ); + } + + + return source; +} + + +/* + ================= + LoadLBM + ================= + */ +void LoadLBM( const char *filename, byte **picture, byte **palette ){ + byte *LBMbuffer, *picbuffer, *cmapbuffer; + int y; + byte *LBM_P, *LBMEND_P; + byte *pic_p; + byte *body_p; + + int formtype,formlength; + int chunktype,chunklength; + +// qiet compiler warnings + picbuffer = NULL; + cmapbuffer = NULL; + +// +// load the LBM +// + LoadFile( filename, (void **)&LBMbuffer ); + +// +// parse the LBM header +// + LBM_P = LBMbuffer; + if ( *(int *)LBMbuffer != LittleLong( FORMID ) ) { + Error( "No FORM ID at start of file!\n" ); + } + + LBM_P += 4; + formlength = BigLong( *(int *)LBM_P ); + LBM_P += 4; + LBMEND_P = LBM_P + Align( formlength ); + + formtype = LittleLong( *(int *)LBM_P ); + + if ( formtype != ILBMID && formtype != PBMID ) { + Error( "Unrecognized form type: %c%c%c%c\n", formtype & 0xff + ,( formtype >> 8 ) & 0xff,( formtype >> 16 ) & 0xff,( formtype >> 24 ) & 0xff ); + } + + LBM_P += 4; + +// +// parse chunks +// + + while ( LBM_P < LBMEND_P ) + { + chunktype = LBM_P[0] + ( LBM_P[1] << 8 ) + ( LBM_P[2] << 16 ) + ( LBM_P[3] << 24 ); + LBM_P += 4; + chunklength = LBM_P[3] + ( LBM_P[2] << 8 ) + ( LBM_P[1] << 16 ) + ( LBM_P[0] << 24 ); + LBM_P += 4; + + switch ( chunktype ) + { + case BMHDID: + memcpy( &bmhd,LBM_P,sizeof( bmhd ) ); + bmhd.w = BigShort( bmhd.w ); + bmhd.h = BigShort( bmhd.h ); + bmhd.x = BigShort( bmhd.x ); + bmhd.y = BigShort( bmhd.y ); + bmhd.pageWidth = BigShort( bmhd.pageWidth ); + bmhd.pageHeight = BigShort( bmhd.pageHeight ); + break; + + case CMAPID: + cmapbuffer = safe_malloc( 768 ); + memset( cmapbuffer, 0, 768 ); + memcpy( cmapbuffer, LBM_P, chunklength ); + break; + + case BODYID: + body_p = LBM_P; + + pic_p = picbuffer = safe_malloc( bmhd.w * bmhd.h ); + if ( formtype == PBMID ) { + // + // unpack PBM + // + for ( y = 0 ; y < bmhd.h ; y++, pic_p += bmhd.w ) + { + if ( bmhd.compression == cm_rle1 ) { + body_p = LBMRLEDecompress( (byte *)body_p + , pic_p, bmhd.w ); + } + else if ( bmhd.compression == cm_none ) { + memcpy( pic_p,body_p,bmhd.w ); + body_p += Align( bmhd.w ); + } + } + + } + else + { + // + // unpack ILBM + // + Error( "%s is an interlaced LBM, not packed", filename ); + } + break; + } + + LBM_P += Align( chunklength ); + } + + free( LBMbuffer ); + + *picture = picbuffer; + + if ( palette ) { + *palette = cmapbuffer; + } +} + + +/* + ============================================================================ + + WRITE LBM + + ============================================================================ + */ + +/* + ============== + WriteLBMfile + ============== + */ +void WriteLBMfile( const char *filename, byte *data, + int width, int height, byte *palette ){ + byte *lbm, *lbmptr; + int *formlength, *bmhdlength, *cmaplength, *bodylength; + int length; + bmhd_t basebmhd; + + lbm = lbmptr = safe_malloc( width * height + 1000 ); + +// +// start FORM +// + *lbmptr++ = 'F'; + *lbmptr++ = 'O'; + *lbmptr++ = 'R'; + *lbmptr++ = 'M'; + + formlength = (int*)lbmptr; + lbmptr += 4; // leave space for length + + *lbmptr++ = 'P'; + *lbmptr++ = 'B'; + *lbmptr++ = 'M'; + *lbmptr++ = ' '; + +// +// write BMHD +// + *lbmptr++ = 'B'; + *lbmptr++ = 'M'; + *lbmptr++ = 'H'; + *lbmptr++ = 'D'; + + bmhdlength = (int *)lbmptr; + lbmptr += 4; // leave space for length + + memset( &basebmhd,0,sizeof( basebmhd ) ); + basebmhd.w = BigShort( (short)width ); + basebmhd.h = BigShort( (short)height ); + basebmhd.nPlanes = BigShort( 8 ); + basebmhd.xAspect = BigShort( 5 ); + basebmhd.yAspect = BigShort( 6 ); + basebmhd.pageWidth = BigShort( (short)width ); + basebmhd.pageHeight = BigShort( (short)height ); + + memcpy( lbmptr,&basebmhd,sizeof( basebmhd ) ); + lbmptr += sizeof( basebmhd ); + + length = lbmptr - (byte *)bmhdlength - 4; + *bmhdlength = BigLong( length ); + if ( length & 1 ) { + *lbmptr++ = 0; // pad chunk to even offset + + } +// +// write CMAP +// + *lbmptr++ = 'C'; + *lbmptr++ = 'M'; + *lbmptr++ = 'A'; + *lbmptr++ = 'P'; + + cmaplength = (int *)lbmptr; + lbmptr += 4; // leave space for length + + memcpy( lbmptr,palette,768 ); + lbmptr += 768; + + length = lbmptr - (byte *)cmaplength - 4; + *cmaplength = BigLong( length ); + if ( length & 1 ) { + *lbmptr++ = 0; // pad chunk to even offset + + } +// +// write BODY +// + *lbmptr++ = 'B'; + *lbmptr++ = 'O'; + *lbmptr++ = 'D'; + *lbmptr++ = 'Y'; + + bodylength = (int *)lbmptr; + lbmptr += 4; // leave space for length + + memcpy( lbmptr,data,width * height ); + lbmptr += width * height; + + length = lbmptr - (byte *)bodylength - 4; + *bodylength = BigLong( length ); + if ( length & 1 ) { + *lbmptr++ = 0; // pad chunk to even offset + + } +// +// done +// + length = lbmptr - (byte *)formlength - 4; + *formlength = BigLong( length ); + if ( length & 1 ) { + *lbmptr++ = 0; // pad chunk to even offset + + } +// +// write output file +// + SaveFile( filename, lbm, lbmptr - lbm ); + free( lbm ); +} + + +/* + ============================================================================ + + LOAD PCX + + ============================================================================ + */ + +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* + ============== + LoadPCX + ============== + */ + +/* RR2DO2 */ +#define DECODEPCX( b, d, r ) d = *b++; if ( ( d & 0xC0 ) == 0xC0 ) {r = d & 0x3F; d = *b++; }else{r = 1; } + +void LoadPCX( const char *filename, byte **pic, byte **palette, int *width, int *height ){ + byte *raw; + pcx_t *pcx; + int x, y, lsize; + int len; + int dataByte, runLength; + byte *out, *pix; + + + /* load the file */ + len = vfsLoadFile( filename, (void **)&raw, 0 ); + if ( len == -1 ) { + Error( "LoadPCX: Couldn't read %s", filename ); + } + + + /* parse the PCX file */ + pcx = (pcx_t *)raw; + raw = &pcx->data; + + pcx->xmin = LittleShort( pcx->xmin ); + pcx->ymin = LittleShort( pcx->ymin ); + pcx->xmax = LittleShort( pcx->xmax ); + pcx->ymax = LittleShort( pcx->ymax ); + pcx->hres = LittleShort( pcx->hres ); + pcx->vres = LittleShort( pcx->vres ); + pcx->bytes_per_line = LittleShort( pcx->bytes_per_line ); + pcx->palette_type = LittleShort( pcx->palette_type ); + + if ( pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->bits_per_pixel != 8 + || pcx->xmax >= 640 + || pcx->ymax >= 480 ) { + Error( "Bad pcx file %s", filename ); + } + + if ( palette ) { + *palette = safe_malloc( 768 ); + memcpy( *palette, (byte *)pcx + len - 768, 768 ); + } + + if ( width ) { + *width = pcx->xmax + 1; + } + if ( height ) { + *height = pcx->ymax + 1; + } + + if ( !pic ) { + return; + } + + out = safe_malloc( ( pcx->ymax + 1 ) * ( pcx->xmax + 1 ) ); + if ( !out ) { + Error( "LoadPCX: couldn't allocate" ); + } + + *pic = out; + pix = out; + + /* RR2DO2: pcx fix */ + lsize = pcx->color_planes * pcx->bytes_per_line; + + /* go scanline by scanline */ + for ( y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1 ) + { + /* do a scanline */ + runLength = 0; + for ( x = 0; x <= pcx->xmax; ) + { + /* RR2DO2 */ + DECODEPCX( raw, dataByte, runLength ); + while ( runLength-- > 0 ) + pix[ x++ ] = dataByte; + } + + /* RR2DO2: discard any other data */ + while ( x < lsize ) + { + DECODEPCX( raw, dataByte, runLength ); + x++; + } + while ( runLength-- > 0 ) + x++; + } + + /* validity check */ + if ( raw - (byte *) pcx > len ) { + Error( "PCX file %s was malformed", filename ); + } + free( pcx ); +} + + + +/* + ============== + WritePCXfile + ============== + */ +void WritePCXfile( const char *filename, byte *data, + int width, int height, byte *palette ){ + int i, j, length; + pcx_t *pcx; + byte *pack; + + pcx = safe_malloc( width * height * 2 + 1000 ); + memset( pcx, 0, sizeof( *pcx ) ); + + pcx->manufacturer = 0x0a; // PCX id + pcx->version = 5; // 256 color + pcx->encoding = 1; // uncompressed + pcx->bits_per_pixel = 8; // 256 color + pcx->xmin = 0; + pcx->ymin = 0; + pcx->xmax = LittleShort( (short)( width - 1 ) ); + pcx->ymax = LittleShort( (short)( height - 1 ) ); + pcx->hres = LittleShort( (short)width ); + pcx->vres = LittleShort( (short)height ); + pcx->color_planes = 1; // chunky image + pcx->bytes_per_line = LittleShort( (short)width ); + pcx->palette_type = LittleShort( 1 ); // not a grey scale + + // pack the image + pack = &pcx->data; + + for ( i = 0 ; i < height ; i++ ) + { + for ( j = 0 ; j < width ; j++ ) + { + if ( ( *data & 0xc0 ) != 0xc0 ) { + *pack++ = *data++; + } + else + { + *pack++ = 0xc1; + *pack++ = *data++; + } + } + } + + // write the palette + *pack++ = 0x0c; // palette ID byte + for ( i = 0 ; i < 768 ; i++ ) + *pack++ = *palette++; + +// write output file + length = pack - (byte *)pcx; + SaveFile( filename, pcx, length ); + + free( pcx ); +} + +/* + ============================================================================ + + LOAD BMP + + ============================================================================ + */ + + +/* + + // we can't just use these structures, because + // compiler structure alignment will not be portable + // on this unaligned stuff + + typedef struct tagBITMAPFILEHEADER { // bmfh + WORD bfType; // BM + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; + } BITMAPFILEHEADER; + + typedef struct tagBITMAPINFOHEADER{ // bmih + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; + } BITMAPINFOHEADER; + + typedef struct tagBITMAPINFO { // bmi + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; + } BITMAPINFO; + + typedef struct tagBITMAPCOREHEADER { // bmch + DWORD bcSize; + WORD bcWidth; + WORD bcHeight; + WORD bcPlanes; + WORD bcBitCount; + } BITMAPCOREHEADER; + + typedef struct _BITMAPCOREINFO { // bmci + BITMAPCOREHEADER bmciHeader; + RGBTRIPLE bmciColors[1]; + } BITMAPCOREINFO; + + */ + +/* + ============== + LoadBMP + ============== + */ +void LoadBMP( const char *filename, byte **pic, byte **palette, int *width, int *height ){ + byte *out; + int i; + int bfOffBits; + int structSize; + int bcWidth; + int bcHeight; + int bcPlanes; + int bcBitCount; + byte bcPalette[1024]; + qboolean flipped; + byte *in; + int len, pos = 0; + + len = vfsLoadFile( filename, (void **)&in, 0 ); + if ( len == -1 ) { + Error( "Couldn't read %s", filename ); + } + + i = bufLittleShort( in, len, &pos ); + if ( i != 'B' + ( 'M' << 8 ) ) { + Error( "%s is not a bmp file", filename ); + } + + /* bfSize = */ bufLittleLong( in, len, &pos ); + bufLittleShort( in, len, &pos ); + bufLittleShort( in, len, &pos ); + bfOffBits = bufLittleLong( in, len, &pos ); + + // the size will tell us if it is a + // bitmapinfo or a bitmapcore + structSize = bufLittleLong( in, len, &pos ); + if ( structSize == 40 ) { + // bitmapinfo + bcWidth = bufLittleLong( in, len, &pos ); + bcHeight = bufLittleLong( in, len, &pos ); + bcPlanes = bufLittleShort( in, len, &pos ); + bcBitCount = bufLittleShort( in, len, &pos ); + + pos += 24; + + if ( palette ) { + memcpy( bcPalette, in + pos, 1024 ); + pos += 1024; + *palette = safe_malloc( 768 ); + + for ( i = 0 ; i < 256 ; i++ ) + { + ( *palette )[i * 3 + 0] = bcPalette[i * 4 + 2]; + ( *palette )[i * 3 + 1] = bcPalette[i * 4 + 1]; + ( *palette )[i * 3 + 2] = bcPalette[i * 4 + 0]; + } + } + } + else if ( structSize == 12 ) { + // bitmapcore + bcWidth = bufLittleShort( in, len, &pos ); + bcHeight = bufLittleShort( in, len, &pos ); + bcPlanes = bufLittleShort( in, len, &pos ); + bcBitCount = bufLittleShort( in, len, &pos ); + + if ( palette ) { + memcpy( bcPalette, in + pos, 768 ); + pos += 768; + *palette = safe_malloc( 768 ); + + for ( i = 0 ; i < 256 ; i++ ) { + ( *palette )[i * 3 + 0] = bcPalette[i * 3 + 2]; + ( *palette )[i * 3 + 1] = bcPalette[i * 3 + 1]; + ( *palette )[i * 3 + 2] = bcPalette[i * 3 + 0]; + } + } + } + else { + Error( "%s had strange struct size", filename ); + } + + if ( bcPlanes != 1 ) { + Error( "%s was not a single plane image", filename ); + } + + if ( bcBitCount != 8 ) { + Error( "%s was not an 8 bit image", filename ); + } + + if ( bcHeight < 0 ) { + bcHeight = -bcHeight; + flipped = qtrue; + } + else { + flipped = qfalse; + } + + if ( width ) { + *width = bcWidth; + } + if ( height ) { + *height = bcHeight; + } + + if ( !pic ) { + free( in ); + return; + } + + out = safe_malloc( bcWidth * bcHeight ); + *pic = out; + pos = bfOffBits; + + if ( flipped ) { + for ( i = 0 ; i < bcHeight ; i++ ) { + memcpy( out + bcWidth * ( bcHeight - 1 - i ), in + pos, bcWidth ); + pos += bcWidth; + } + } + else { + memcpy( out, in + pos, bcWidth * bcHeight ); + pos += bcWidth * bcHeight; + } + + free( in ); +} + + +/* + ============================================================================ + + LOAD IMAGE + + ============================================================================ + */ + +/* + ============== + Load256Image + + Will load either an lbm or pcx, depending on extension. + Any of the return pointers can be NULL if you don't want them. + ============== + */ +void Load256Image( const char *name, byte **pixels, byte **palette, int *width, int *height ){ + char ext[128]; + + ExtractFileExtension( name, ext ); + if ( !Q_stricmp( ext, "lbm" ) ) { + LoadLBM( name, pixels, palette ); + if ( width ) { + *width = bmhd.w; + } + if ( height ) { + *height = bmhd.h; + } + } + else if ( !Q_stricmp( ext, "pcx" ) ) { + LoadPCX( name, pixels, palette, width, height ); + } + else if ( !Q_stricmp( ext, "bmp" ) ) { + LoadBMP( name, pixels, palette, width, height ); + } + else{ + Error( "%s doesn't have a known image extension", name ); + } +} + + +/* + ============== + Save256Image + + Will save either an lbm or pcx, depending on extension. + ============== + */ +void Save256Image( const char *name, byte *pixels, byte *palette, + int width, int height ){ + char ext[128]; + + ExtractFileExtension( name, ext ); + if ( !Q_stricmp( ext, "lbm" ) ) { + WriteLBMfile( name, pixels, width, height, palette ); + } + else if ( !Q_stricmp( ext, "pcx" ) ) { + WritePCXfile( name, pixels, width, height, palette ); + } + else{ + Error( "%s doesn't have a known image extension", name ); + } +} + + + + +/* + ============================================================================ + + TARGA IMAGE + + ============================================================================ + */ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + +void TargaError( TargaHeader *t, const char *message ){ + Sys_Printf( "%s\n:TargaHeader:\nuint8 id_length = %i;\nuint8 colormap_type = %i;\nuint8 image_type = %i;\nuint16 colormap_index = %i;\nuint16 colormap_length = %i;\nuint8 colormap_size = %i;\nuint16 x_origin = %i;\nuint16 y_origin = %i;\nuint16 width = %i;\nuint16 height = %i;\nuint8 pixel_size = %i;\nuint8 attributes = %i;\n", message, t->id_length, t->colormap_type, t->image_type, t->colormap_index, t->colormap_length, t->colormap_size, t->x_origin, t->y_origin, t->width, t->height, t->pixel_size, t->attributes ); +} + +/* + ============= + LoadTGABuffer + ============= + */ +void LoadTGABuffer( const byte *f, const byte *enddata, byte **pic, int *width, int *height ){ + int x, y, row_inc, compressed, readpixelcount, red, green, blue, alpha, runlen, pindex, alphabits, image_width, image_height; + byte *pixbuf, *image_rgba; + const byte *fin; + unsigned char *p; + TargaHeader targa_header; + unsigned char palette[256 * 4]; + + *pic = NULL; + + // abort if it is too small to parse + if ( enddata - f < 19 ) { + return; + } + + targa_header.id_length = f[0]; + targa_header.colormap_type = f[1]; + targa_header.image_type = f[2]; + + targa_header.colormap_index = f[3] + f[4] * 256; + targa_header.colormap_length = f[5] + f[6] * 256; + targa_header.colormap_size = f[7]; + targa_header.x_origin = f[8] + f[9] * 256; + targa_header.y_origin = f[10] + f[11] * 256; + targa_header.width = image_width = f[12] + f[13] * 256; + targa_header.height = image_height = f[14] + f[15] * 256; + + targa_header.pixel_size = f[16]; + targa_header.attributes = f[17]; + + // advance to end of header + fin = f + 18; + + // skip TARGA image comment (usually 0 bytes) + fin += targa_header.id_length; + + // read/skip the colormap if present (note: according to the TARGA spec it + // can be present even on truecolor or greyscale images, just not used by + // the image data) + if ( targa_header.colormap_type ) { + if ( targa_header.colormap_length > 256 ) { + TargaError( &targa_header, "LoadTGA: only up to 256 colormap_length supported\n" ); + return; + } + if ( targa_header.colormap_index ) { + TargaError( &targa_header, "LoadTGA: colormap_index not supported\n" ); + return; + } + if ( targa_header.colormap_size == 24 ) { + for ( x = 0; x < targa_header.colormap_length; x++ ) + { + palette[x * 4 + 2] = *fin++; + palette[x * 4 + 1] = *fin++; + palette[x * 4 + 0] = *fin++; + palette[x * 4 + 3] = 255; + } + } + else if ( targa_header.colormap_size == 32 ) { + for ( x = 0; x < targa_header.colormap_length; x++ ) + { + palette[x * 4 + 2] = *fin++; + palette[x * 4 + 1] = *fin++; + palette[x * 4 + 0] = *fin++; + palette[x * 4 + 3] = *fin++; + } + } + else + { + TargaError( &targa_header, "LoadTGA: Only 32 and 24 bit colormap_size supported\n" ); + return; + } + } + + // check our pixel_size restrictions according to image_type + if ( targa_header.image_type == 2 || targa_header.image_type == 10 ) { + if ( targa_header.pixel_size != 24 && targa_header.pixel_size != 32 ) { + TargaError( &targa_header, "LoadTGA: only 24bit and 32bit pixel sizes supported for type 2 and type 10 images\n" ); + return; + } + } + else if ( targa_header.image_type == 1 || targa_header.image_type == 9 ) { + if ( targa_header.pixel_size != 8 ) { + TargaError( &targa_header, "LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n" ); + return; + } + } + else if ( targa_header.image_type == 3 || targa_header.image_type == 11 ) { + if ( targa_header.pixel_size != 8 ) { + TargaError( &targa_header, "LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n" ); + return; + } + } + else + { + TargaError( &targa_header, "LoadTGA: Only type 1, 2, 3, 9, 10, and 11 targa RGB images supported" ); + return; + } + + if ( targa_header.attributes & 0x10 ) { + TargaError( &targa_header, "LoadTGA: origin must be in top left or bottom left, top right and bottom right are not supported\n" ); + return; + } + + // number of attribute bits per pixel, we only support 0 or 8 + alphabits = targa_header.attributes & 0x0F; + if ( alphabits != 8 && alphabits != 0 ) { + TargaError( &targa_header, "LoadTGA: only 0 or 8 attribute (alpha) bits supported\n" ); + return; + } + + image_rgba = safe_malloc( image_width * image_height * 4 ); + if ( !image_rgba ) { + Sys_Printf( "LoadTGA: not enough memory for %i by %i image\n", image_width, image_height ); + return; + } + + // If bit 5 of attributes isn't set, the image has been stored from bottom to top + if ( ( targa_header.attributes & 0x20 ) == 0 ) { + pixbuf = image_rgba + ( image_height - 1 ) * image_width * 4; + row_inc = -image_width * 4 * 2; + } + else + { + pixbuf = image_rgba; + row_inc = 0; + } + + compressed = targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11; + x = 0; + y = 0; + red = green = blue = alpha = 255; + while ( y < image_height ) + { + // decoder is mostly the same whether it's compressed or not + readpixelcount = 1000000; + runlen = 1000000; + if ( compressed && fin < enddata ) { + runlen = *fin++; + // high bit indicates this is an RLE compressed run + if ( runlen & 0x80 ) { + readpixelcount = 1; + } + runlen = 1 + ( runlen & 0x7f ); + } + + while ( ( runlen-- ) && y < image_height ) + { + if ( readpixelcount > 0 ) { + readpixelcount--; + red = green = blue = alpha = 255; + if ( fin < enddata ) { + switch ( targa_header.image_type ) + { + case 1: + case 9: + // colormapped + pindex = *fin++; + if ( pindex >= targa_header.colormap_length ) { + pindex = 0; // error + } + p = palette + pindex * 4; + red = p[0]; + green = p[1]; + blue = p[2]; + alpha = p[3]; + break; + case 2: + case 10: + // BGR or BGRA + blue = *fin++; + if ( fin < enddata ) { + green = *fin++; + } + if ( fin < enddata ) { + red = *fin++; + } + if ( targa_header.pixel_size == 32 && fin < enddata ) { + alpha = *fin++; + } + break; + case 3: + case 11: + // greyscale + red = green = blue = *fin++; + break; + } + if ( !alphabits ) { + alpha = 255; + } + } + } + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + x++; + if ( x == image_width ) { + // end of line, advance to next + x = 0; + y++; + pixbuf += row_inc; + } + } + } + + *pic = image_rgba; + if ( width ) { + *width = image_width; + } + if ( height ) { + *height = image_height; + } +} + + +/* + ============= + LoadTGA + ============= + */ +void LoadTGA( const char *name, byte **pixels, int *width, int *height ){ + byte *buffer; + int nLen; + // + // load the file + // + nLen = vfsLoadFile( name, (void **)&buffer, 0 ); + if ( nLen == -1 ) { + Error( "Couldn't read %s", name ); + } + + LoadTGABuffer( buffer, buffer + nLen, pixels, width, height ); + +} + + +/* + ================ + WriteTGA + ================ + */ +void WriteTGA( const char *filename, byte *data, int width, int height ) { + byte *buffer; + int i; + int c; + FILE *f; + + buffer = safe_malloc( width * height * 4 + 18 ); + memset( buffer, 0, 18 ); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 32; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 4; + for ( i = 18 ; i < c ; i += 4 ) + { + buffer[i] = data[i - 18 + 2]; // blue + buffer[i + 1] = data[i - 18 + 1]; // green + buffer[i + 2] = data[i - 18 + 0]; // red + buffer[i + 3] = data[i - 18 + 3]; // alpha + } + + f = fopen( filename, "wb" ); + fwrite( buffer, 1, c, f ); + fclose( f ); + + free( buffer ); +} + +void WriteTGAGray( const char *filename, byte *data, int width, int height ) { + byte buffer[18]; + FILE *f; + + memset( buffer, 0, 18 ); + buffer[2] = 3; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 8; // pixel size + + f = fopen( filename, "wb" ); + fwrite( buffer, 1, 18, f ); + fwrite( data, 1, width * height, f ); + fclose( f ); +} + +/* + ============================================================================ + + LOAD32BITIMAGE + + ============================================================================ + */ + +/* + ============== + Load32BitImage + + Any of the return pointers can be NULL if you don't want them. + ============== + */ +void Load32BitImage( const char *name, unsigned **pixels, int *width, int *height ){ + char ext[128]; + byte *palette; + byte *pixels8; + byte *pixels32; + int size; + int i; + int v; + + ExtractFileExtension( name, ext ); + if ( !Q_stricmp( ext, "tga" ) ) { + LoadTGA( name, (byte **)pixels, width, height ); + } + else { + Load256Image( name, &pixels8, &palette, width, height ); + if ( !pixels ) { + return; + } + size = *width * *height; + pixels32 = safe_malloc( size * 4 ); + *pixels = (unsigned *)pixels32; + for ( i = 0 ; i < size ; i++ ) { + v = pixels8[i]; + pixels32[i * 4 + 0] = palette[ v * 3 + 0 ]; + pixels32[i * 4 + 1] = palette[ v * 3 + 1 ]; + pixels32[i * 4 + 2] = palette[ v * 3 + 2 ]; + pixels32[i * 4 + 3] = 0xff; + } + } +} + + +/* + ============================================================================ + + KHRONOS TEXTURE + + ============================================================================ + */ + + +#define KTX_UINT32_LE( buf ) ( ( unsigned int )( (buf)[0] | ( (buf)[1] << 8 ) | ( (buf)[2] << 16 ) | ( (buf)[3] << 24 ) ) ) +#define KTX_UINT32_BE( buf ) ( ( unsigned int )( (buf)[3] | ( (buf)[2] << 8 ) | ( (buf)[1] << 16 ) | ( (buf)[0] << 24 ) ) ) + +#define KTX_TYPE_UNSIGNED_BYTE 0x1401 +#define KTX_TYPE_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define KTX_TYPE_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define KTX_TYPE_UNSIGNED_SHORT_5_6_5 0x8363 + +#define KTX_FORMAT_ALPHA 0x1906 +#define KTX_FORMAT_RGB 0x1907 +#define KTX_FORMAT_RGBA 0x1908 +#define KTX_FORMAT_LUMINANCE 0x1909 +#define KTX_FORMAT_LUMINANCE_ALPHA 0x190A +#define KTX_FORMAT_BGR 0x80E0 +#define KTX_FORMAT_BGRA 0x80E1 + +#define KTX_FORMAT_ETC1_RGB8 0x8D64 + +static void KTX_DecodeA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = out[1] = out[2] = 0; + out[3] = in[0]; +} + +static void KTX_DecodeRGB8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = 255; +} + +static void KTX_DecodeRGBA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; +} + +static void KTX_DecodeL8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = out[1] = out[2] = in[0]; + out[3] = 255; +} + +static void KTX_DecodeLA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = out[1] = out[2] = in[0]; + out[3] = in[1]; +} + +static void KTX_DecodeBGR8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[2]; + out[1] = in[1]; + out[2] = in[0]; + out[3] = 255; +} + +static void KTX_DecodeBGRA8( const byte *in, qboolean bigEndian, byte *out ){ + out[0] = in[2]; + out[1] = in[1]; + out[2] = in[0]; + out[3] = in[3]; +} + +static void KTX_DecodeRGBA4( const byte *in, qboolean bigEndian, byte *out ){ + unsigned short rgba; + int r, g, b, a; + + if ( bigEndian ) { + rgba = ( in[0] << 8 ) | in[1]; + } + else { + rgba = ( in[1] << 8 ) | in[0]; + } + + r = ( rgba >> 12 ) & 0xf; + g = ( rgba >> 8 ) & 0xf; + b = ( rgba >> 4 ) & 0xf; + a = rgba & 0xf; + out[0] = ( r << 4 ) | r; + out[1] = ( g << 4 ) | g; + out[2] = ( b << 4 ) | b; + out[3] = ( a << 4 ) | a; +} + +static void KTX_DecodeRGBA5( const byte *in, qboolean bigEndian, byte *out ){ + unsigned short rgba; + int r, g, b; + + if ( bigEndian ) { + rgba = ( in[0] << 8 ) | in[1]; + } + else { + rgba = ( in[1] << 8 ) | in[0]; + } + + r = ( rgba >> 11 ) & 0x1f; + g = ( rgba >> 6 ) & 0x1f; + b = ( rgba >> 1 ) & 0x1f; + out[0] = ( r << 3 ) | ( r >> 2 ); + out[1] = ( g << 3 ) | ( g >> 2 ); + out[2] = ( b << 3 ) | ( b >> 2 ); + out[3] = ( rgba & 1 ) * 255; +} + +static void KTX_DecodeRGB5( const byte *in, qboolean bigEndian, byte *out ){ + unsigned short rgba; + int r, g, b; + + if ( bigEndian ) { + rgba = ( in[0] << 8 ) | in[1]; + } + else { + rgba = ( in[1] << 8 ) | in[0]; + } + + r = ( rgba >> 11 ) & 0x1f; + g = ( rgba >> 5 ) & 0x3f; + b = rgba & 0x1f; + out[0] = ( r << 3 ) | ( r >> 2 ); + out[1] = ( g << 2 ) | ( g >> 4 ); + out[2] = ( b << 3 ) | ( b >> 2 ); + out[3] = 255; +} + +typedef struct +{ + unsigned int type; + unsigned int format; + unsigned int pixelSize; + void ( *decode )( const byte *in, qboolean bigEndian, byte *out ); +} KTX_UncompressedFormat_t; + +static const KTX_UncompressedFormat_t KTX_UncompressedFormats[] = +{ + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_ALPHA, 1, KTX_DecodeA8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_RGB, 3, KTX_DecodeRGB8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_RGBA, 4, KTX_DecodeRGBA8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_LUMINANCE, 1, KTX_DecodeL8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_LUMINANCE_ALPHA, 2, KTX_DecodeLA8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_BGR, 3, KTX_DecodeBGR8 }, + { KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_BGRA, 4, KTX_DecodeBGRA8 }, + { KTX_TYPE_UNSIGNED_SHORT_4_4_4_4, KTX_FORMAT_RGBA, 2, KTX_DecodeRGBA4 }, + { KTX_TYPE_UNSIGNED_SHORT_5_5_5_1, KTX_FORMAT_RGBA, 2, KTX_DecodeRGBA5 }, + { KTX_TYPE_UNSIGNED_SHORT_5_6_5, KTX_FORMAT_RGB, 2, KTX_DecodeRGB5 }, + { 0, 0, 0, NULL } +}; + +static qboolean KTX_DecodeETC1( const byte* in, size_t inSize, unsigned int width, unsigned int height, byte* out ){ + unsigned int y, stride = width * 4; + byte rgba[64]; + + if ( inSize < ( ( ( ( width + 3 ) & ~3 ) * ( ( height + 3 ) & ~3 ) ) >> 1 ) ) { + return qfalse; + } + + for ( y = 0; y < height; y += 4, out += stride * 4 ) + { + byte *p; + unsigned int x, blockrows; + + blockrows = height - y; + if ( blockrows > 4 ) { + blockrows = 4; + } + + p = out; + for ( x = 0; x < width; x += 4, p += 16 ) + { + unsigned int blockrowsize, blockrow; + + ETC_DecodeETC1Block( in, rgba, qtrue ); + in += 8; + + blockrowsize = width - x; + if ( blockrowsize > 4 ) { + blockrowsize = 4; + } + blockrowsize *= 4; + for ( blockrow = 0; blockrow < blockrows; blockrow++ ) + { + memcpy( p + blockrow * stride, rgba + blockrow * 16, blockrowsize ); + } + } + } + + return qtrue; +} + +#define KTX_HEADER_UINT32( buf ) ( bigEndian ? KTX_UINT32_BE( buf ) : KTX_UINT32_LE( buf ) ) + +void LoadKTXBufferFirstImage( const byte *buffer, size_t bufSize, byte **pic, int *picWidth, int *picHeight ){ + unsigned int type, format, width, height, imageOffset; + byte *pixels; + + if ( bufSize < 64 ) { + Error( "LoadKTX: Image doesn't have a header" ); + } + + if ( memcmp( buffer, "\xABKTX 11\xBB\r\n\x1A\n", 12 ) ) { + Error( "LoadKTX: Image has the wrong identifier" ); + } + + qboolean bigEndian = ( buffer[4] == 4 ); + + type = KTX_HEADER_UINT32( buffer + 16 ); + if ( type ) { + format = KTX_HEADER_UINT32( buffer + 32 ); + } + else { + format = KTX_HEADER_UINT32( buffer + 28 ); + } + + width = KTX_HEADER_UINT32( buffer + 36 ); + height = KTX_HEADER_UINT32( buffer + 40 ); + if ( !width ) { + Error( "LoadKTX: Image has zero width" ); + } + if ( !height ) { + height = 1; + } + if ( picWidth ) { + *picWidth = width; + } + if ( picHeight ) { + *picHeight = height; + } + + imageOffset = 64 + KTX_HEADER_UINT32( buffer + 60 ) + 4; + if ( bufSize < imageOffset ) { + Error( "LoadKTX: No image in the file" ); + } + buffer += imageOffset; + bufSize -= imageOffset; + + pixels = safe_malloc( width * height * 4 ); + *pic = pixels; + + if ( type ) { + const KTX_UncompressedFormat_t *ktxFormat = KTX_UncompressedFormats; + unsigned int pixelSize; + unsigned int inRowLength, inPadding; + unsigned int y; + + while ( ktxFormat->type ) + { + if ( ktxFormat->type == type && ktxFormat->format == format ) { + break; + } + ktxFormat++; + } + if ( !ktxFormat->type ) { + Error( "LoadKTX: Image has an unsupported pixel type 0x%X or format 0x%X", type, format ); + } + + pixelSize = ktxFormat->pixelSize; + inRowLength = width * pixelSize; + inPadding = ( ( inRowLength + 3 ) & ~3 ) - inRowLength; + + if ( bufSize < height * ( inRowLength + inPadding ) ) { + Error( "LoadKTX: Image is truncated" ); + } + + for ( y = 0; y < height; y++ ) + { + unsigned int x; + for ( x = 0; x < width; x++, buffer += pixelSize, pixels += 4 ) + { + ktxFormat->decode( buffer, bigEndian, pixels ); + } + buffer += inPadding; + } + } + else { + qboolean decoded = qfalse; + + switch ( format ) + { + case KTX_FORMAT_ETC1_RGB8: + decoded = KTX_DecodeETC1( buffer, bufSize, width, height, pixels ); + break; + default: + Error( "LoadKTX: Image has an unsupported compressed format format 0x%X", format ); + break; + } + + if ( !decoded ) { + Error( "LoadKTX: Image is truncated" ); + } + } +} diff --git a/tools/common/imagelib.h b/tools/common/imagelib.h new file mode 100644 index 0000000..2c028bf --- /dev/null +++ b/tools/common/imagelib.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// piclib.h + + +void LoadLBM( const char *filename, byte **picture, byte **palette ); +void WriteLBMfile( const char *filename, byte *data, int width, int height + , byte *palette ); +void LoadPCX( const char *filename, byte **picture, byte **palette, int *width, int *height ); +void WritePCXfile( const char *filename, byte *data, int width, int height + , byte *palette ); + +// loads / saves either lbm or pcx, depending on extension +void Load256Image( const char *name, byte **pixels, byte **palette, + int *width, int *height ); +void Save256Image( const char *name, byte *pixels, byte *palette, + int width, int height ); + + +void LoadTGA( const char *filename, byte **pixels, int *width, int *height ); +void LoadTGABuffer( const byte *buffer, const byte* enddata, byte **pic, int *width, int *height ); +void WriteTGA( const char *filename, byte *data, int width, int height ); +void WriteTGAGray( const char *filename, byte *data, int width, int height ); +int LoadJPGBuff( void *src_buffer, int src_size, unsigned char **pic, int *width, int *height ); + +void Load32BitImage( const char *name, unsigned **pixels, int *width, int *height ); + +void LoadKTXBufferFirstImage( const byte *buffer, size_t bufSize, byte **pic, int *picWidth, int *picHeight ); diff --git a/tools/common/inout.c b/tools/common/inout.c new file mode 100644 index 0000000..58d42ed --- /dev/null +++ b/tools/common/inout.c @@ -0,0 +1,364 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//----------------------------------------------------------------------------- +// +// +// DESCRIPTION: +// deal with in/out tasks, for either stdin/stdout or network/XML stream +// + +#include "globaldefs.h" +#include "cmdlib.h" +#include "mathlib.h" +#include "polylib.h" +#include "inout.h" +#include +#include + +#if GDEF_OS_WINDOWS +#include +#include +#endif + +// network broadcasting +#include "l_net/l_net.h" +#include "libxml/tree.h" + +// utf8 conversion +#include + +#if GDEF_OS_WINDOWS +HWND hwndOut = NULL; +qboolean lookedForServer = qfalse; +UINT wm_BroadcastCommand = -1; +#endif + +socket_t *brdcst_socket; +netmessage_t msg; + +qboolean verbose = qfalse; + +// our main document +// is streamed through the network to Radiant +// possibly written to disk at the end of the run +//++timo FIXME: need to be global, required when creating nodes? +xmlDocPtr doc; +xmlNodePtr tree; + +// some useful stuff +xmlNodePtr xml_NodeForVec( vec3_t v ){ + xmlNodePtr ret; + char buf[1024]; + + sprintf( buf, "%f %f %f", v[0], v[1], v[2] ); + ret = xmlNewNode( NULL, (xmlChar*)"point" ); + xmlNodeSetContent( ret, (xmlChar*)buf ); + return ret; +} + +// send a node down the stream, add it to the document +void xml_SendNode( xmlNodePtr node ){ + xmlBufferPtr xml_buf; + char xmlbuf[MAX_NETMESSAGE]; // we have to copy content from the xmlBufferPtr into an aux buffer .. that sucks .. + // this index loops through the node buffer + int pos = 0; + int size; + + xmlAddChild( doc->children, node ); + + if ( brdcst_socket ) { + xml_buf = xmlBufferCreate(); + xmlNodeDump( xml_buf, doc, node, 0, 0 ); + + // the XML node might be too big to fit in a single network message + // l_net library defines an upper limit of MAX_NETMESSAGE + // there are some size check errors, so we use MAX_NETMESSAGE-10 to be safe + // if the size of the buffer exceeds MAX_NETMESSAGE-10 we'll send in several network messages + while ( pos < (int)xml_buf->use ) + { + // what size are we gonna send now? + ( xml_buf->use - pos < MAX_NETMESSAGE - 10 ) ? ( size = xml_buf->use - pos ) : ( size = MAX_NETMESSAGE - 10 ); + //++timo just a debug thing + if ( size == MAX_NETMESSAGE - 10 ) { + Sys_FPrintf( SYS_NOXML, "Got to split the buffer\n" ); + } + memcpy( xmlbuf, xml_buf->content + pos, size ); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString( &msg, xmlbuf ); + Net_Send( brdcst_socket, &msg ); + // now that the thing is sent prepare to loop again + pos += size; + } + +#if 0 + // NOTE: the NMSG_WriteString is limited to MAX_NETMESSAGE + // we will need to split into chunks + // (we could also go lower level, in the end it's using send and receiv which are not size limited) + //++timo FIXME: MAX_NETMESSAGE is not exactly the max size we can stick in the message + // there's some tweaking to do in l_net for that .. so let's give us a margin for now + + //++timo we need to handle the case of a buffer too big to fit in a single message + // try without checks for now + if ( xml_buf->use > MAX_NETMESSAGE - 10 ) { + // if we send that we are probably gonna break the stream at the other end.. + // and Error will call right there + //Error( "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use); + Sys_FPrintf( SYS_NOXML, "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use ); + xml_buf->content[xml_buf->use] = '\0'; //++timo this corrupts the buffer but we don't care it's for printing + Sys_FPrintf( SYS_NOXML, xml_buf->content ); + + } + + size = xml_buf->use; + memcpy( xmlbuf, xml_buf->content, size ); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString( &msg, xmlbuf ); + Net_Send( brdcst_socket, &msg ); +#endif + + xmlBufferFree( xml_buf ); + } +} + +void xml_Select( char *msg, int entitynum, int brushnum, qboolean bError ){ + xmlNodePtr node, select; + char buf[1024]; + char level[2]; + + // now build a proper "select" XML node + sprintf( buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg ); + node = xmlNewNode( NULL, (xmlChar*)"select" ); + xmlNodeSetContent( node, (xmlChar*)buf ); + level[0] = (int)'0' + ( bError ? SYS_ERR : SYS_WRN ) ; + level[1] = 0; + xmlSetProp( node, (xmlChar*)"level", (xmlChar*)&level ); + // a 'select' information + sprintf( buf, "%i %i", entitynum, brushnum ); + select = xmlNewNode( NULL, (xmlChar*)"brush" ); + xmlNodeSetContent( select, (xmlChar*)buf ); + xmlAddChild( node, select ); + xml_SendNode( node ); + + sprintf( buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg ); + if ( bError ) { + Error( buf ); + } + else{ + Sys_FPrintf( SYS_NOXML, "%s\n", buf ); + } + +} + +void xml_Point( char *msg, vec3_t pt ){ + xmlNodePtr node, point; + char buf[1024]; + char level[2]; + + node = xmlNewNode( NULL, (xmlChar*)"pointmsg" ); + xmlNodeSetContent( node, (xmlChar*)msg ); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp( node, (xmlChar*)"level", (xmlChar *)&level ); + // a 'point' node + sprintf( buf, "%g %g %g", pt[0], pt[1], pt[2] ); + point = xmlNewNode( NULL, (xmlChar*)"point" ); + xmlNodeSetContent( point, (xmlChar*)buf ); + xmlAddChild( node, point ); + xml_SendNode( node ); + + sprintf( buf, "%s (%g %g %g)", msg, pt[0], pt[1], pt[2] ); + Error( buf ); +} + +#define WINDING_BUFSIZE 2048 +void xml_Winding( char *msg, vec3_t p[], int numpoints, qboolean die ){ + xmlNodePtr node, winding; + char buf[WINDING_BUFSIZE]; + char smlbuf[128]; + char level[2]; + int i; + + node = xmlNewNode( NULL, (xmlChar*)"windingmsg" ); + xmlNodeSetContent( node, (xmlChar*)msg ); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp( node, (xmlChar*)"level", (xmlChar *)&level ); + // a 'winding' node + sprintf( buf, "%i ", numpoints ); + for ( i = 0; i < numpoints; i++ ) + { + sprintf( smlbuf, "(%g %g %g)", p[i][0], p[i][1], p[i][2] ); + // don't overflow + if ( strlen( buf ) + strlen( smlbuf ) > WINDING_BUFSIZE ) { + break; + } + strcat( buf, smlbuf ); + } + + winding = xmlNewNode( NULL, (xmlChar*)"winding" ); + xmlNodeSetContent( winding, (xmlChar*)buf ); + xmlAddChild( node, winding ); + xml_SendNode( node ); + + if ( die ) { + Error( msg ); + } + else + { + Sys_Printf( msg ); + Sys_Printf( "\n" ); + } +} + +// in include +#include "stream_version.h" + +void Broadcast_Setup( const char *dest ){ + address_t address; + char sMsg[1024]; + + Net_Setup(); + Net_StringToAddress( dest, &address ); + brdcst_socket = Net_Connect( &address, 0 ); + if ( brdcst_socket ) { + // send in a header + sprintf( sMsg, "" ); + NMSG_Clear( &msg ); + NMSG_WriteString( &msg, sMsg ); + Net_Send( brdcst_socket, &msg ); + } +} + +void Broadcast_Shutdown(){ + if ( brdcst_socket ) { + Sys_Printf( "Disconnecting\n" ); + Net_Disconnect( brdcst_socket ); + brdcst_socket = NULL; + } +} + +// all output ends up through here +void FPrintf( int flag, char *buf ){ + xmlNodePtr node; + static qboolean bGotXML = qfalse; + char level[2]; + + printf( "%s", buf ); + + // the following part is XML stuff only.. but maybe we don't want that message to go down the XML pipe? + if ( flag == SYS_NOXML ) { + return; + } + + // ouput an XML file of the run + // use the DOM interface to build a tree + /* + + message string + .. various nodes to describe corresponding geometry .. + + */ + if ( !bGotXML ) { + // initialize + doc = xmlNewDoc( (xmlChar*)"1.0" ); + doc->children = xmlNewDocRawNode( doc, NULL, (xmlChar*)"q3map_feedback", NULL ); + bGotXML = qtrue; + } + node = xmlNewNode( NULL, (xmlChar*)"message" ); + { + gchar* utf8 = g_locale_to_utf8( buf, -1, NULL, NULL, NULL ); + xmlNodeSetContent( node, (xmlChar*)utf8 ); + g_free( utf8 ); + } + level[0] = (int)'0' + flag; + level[1] = 0; + xmlSetProp( node, (xmlChar*)"level", (xmlChar *)&level ); + + xml_SendNode( node ); +} + +#ifdef DBG_XML +void DumpXML(){ + xmlSaveFile( "XMLDump.xml", doc ); +} +#endif + +void Sys_FPrintf( int flag, const char *format, ... ){ + char out_buffer[4096]; + va_list argptr; + + if ( ( flag == SYS_VRB ) && ( verbose == qfalse ) ) { + return; + } + + va_start( argptr, format ); + vsprintf( out_buffer, format, argptr ); + va_end( argptr ); + + FPrintf( flag, out_buffer ); +} + +void Sys_Printf( const char *format, ... ){ + char out_buffer[4096]; + va_list argptr; + + va_start( argptr, format ); + vsprintf( out_buffer, format, argptr ); + va_end( argptr ); + + FPrintf( SYS_STD, out_buffer ); +} + +/* + ================= + Error + + For abnormal program terminations + ================= + */ +void Error( const char *error, ... ){ + char out_buffer[4096]; + char tmp[4096]; + va_list argptr; + + va_start( argptr,error ); + vsprintf( tmp, error, argptr ); + va_end( argptr ); + + sprintf( out_buffer, "************ ERROR ************\n%s\n", tmp ); + + FPrintf( SYS_ERR, out_buffer ); + +#ifdef DBG_XML + DumpXML(); +#endif + + //++timo HACK ALERT .. if we shut down too fast the xml stream won't reach the listener. + // a clean solution is to send a sync request node in the stream and wait for an answer before exiting + Sys_Sleep( 1000 ); + + Broadcast_Shutdown(); + + exit( 1 ); +} diff --git a/tools/common/inout.h b/tools/common/inout.h new file mode 100644 index 0000000..eae54d3 --- /dev/null +++ b/tools/common/inout.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __INOUT__ +#define __INOUT__ + +#include "globaldefs.h" +// inout is the only stuff relying on xml, include the headers there +#include "libxml/tree.h" +#include "mathlib.h" + +// some useful xml routines +xmlNodePtr xml_NodeForVec( vec3_t v ); +void xml_SendNode( xmlNodePtr node ); +// print a message in q3map output and send the corresponding select information down the xml stream +// bError: do we end with an error on this one or do we go ahead? +void xml_Select( char *msg, int entitynum, int brushnum, qboolean bError ); +// end q3map with an error message and send a point information in the xml stream +// note: we might want to add a boolean to use this as a warning or an error thing.. +void xml_Winding( char *msg, vec3_t p[], int numpoints, qboolean die ); +void xml_Point( char *msg, vec3_t pt ); + +extern qboolean bNetworkBroadcast; +void Broadcast_Setup( const char *dest ); +void Broadcast_Shutdown(); + +#define SYS_VRB 0 // verbose support (on/off) +#define SYS_STD 1 // standard print level +#define SYS_WRN 2 // warnings +#define SYS_ERR 3 // error +#define SYS_NOXML 4 // don't send that down the XML stream + +extern qboolean verbose; +void Sys_Printf( const char *text, ... ); +void Sys_FPrintf( int flag, const char *text, ... ); + +#if GDEF_DEBUG +#define DBG_XML 1 +#endif + +#ifdef DBG_XML +void DumpXML(); +#endif + +#endif diff --git a/tools/common/jpeg.c b/tools/common/jpeg.c new file mode 100644 index 0000000..2a3026c --- /dev/null +++ b/tools/common/jpeg.c @@ -0,0 +1,385 @@ +/* + 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. + */ + +// +// Functions to load JPEG files from a buffer, based on jdatasrc.c +// +// Leonardo Zide (leo@lokigames.com) +// + +#include +#include +#include +#include + +#include +#include + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + int src_size; + JOCTET * src_buffer; + + 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. + */ + +static void my_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. + */ + +static boolean my_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->src_buffer, nbytes ); + src->src_buffer += nbytes; + src->src_size -= nbytes; + + if ( nbytes <= 0 ) { + if ( src->start_of_file ) { /* Treat empty input file as fatal error */ + ERREXIT( cinfo, JERR_INPUT_EMPTY ); + } + WARNMS( cinfo, JWRN_JPEG_EOF ); + /* Insert a fake EOI marker */ + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } + + 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. + */ + +static void my_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) my_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. + */ + +static void my_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. + */ + +static void jpeg_buffer_src( j_decompress_ptr cinfo, void* buffer, 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 = my_init_source; + src->pub.fill_input_buffer = my_fill_input_buffer; + src->pub.skip_input_data = my_skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = my_term_source; + src->src_buffer = (JOCTET *)buffer; + src->src_size = bufsize; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} + +// ============================================================================= + +static char errormsg[JMSG_LENGTH_MAX]; + +typedef struct my_jpeg_error_mgr +{ + struct jpeg_error_mgr pub; // "public" fields + jmp_buf setjmp_buffer; // for return to caller +} bt_jpeg_error_mgr; + +static void my_jpeg_error_exit( j_common_ptr cinfo ){ + bt_jpeg_error_mgr* myerr = (bt_jpeg_error_mgr*) cinfo->err; + + ( *cinfo->err->format_message )( cinfo, errormsg ); + + longjmp( myerr->setjmp_buffer, 1 ); +} + +// stash a scanline +static void j_putRGBScanline( unsigned char* jpegline, int widthPix, unsigned char* outBuf, int row ){ + int offset = row * widthPix * 4; + int count; + + for ( count = 0; count < widthPix; count++ ) + { + unsigned char iRed, iBlu, iGrn; + unsigned char *oRed, *oBlu, *oGrn, *oAlp; + + iRed = *( jpegline + count * 3 + 0 ); + iGrn = *( jpegline + count * 3 + 1 ); + iBlu = *( jpegline + count * 3 + 2 ); + + oRed = outBuf + offset + count * 4 + 0; + oGrn = outBuf + offset + count * 4 + 1; + oBlu = outBuf + offset + count * 4 + 2; + oAlp = outBuf + offset + count * 4 + 3; + + *oRed = iRed; + *oGrn = iGrn; + *oBlu = iBlu; + *oAlp = 255; + } +} + +// stash a scanline +static void j_putRGBAScanline( unsigned char* jpegline, int widthPix, unsigned char* outBuf, int row ){ + int offset = row * widthPix * 4; + int count; + + for ( count = 0; count < widthPix; count++ ) + { + unsigned char iRed, iBlu, iGrn /* , iAlp */; + unsigned char *oRed, *oBlu, *oGrn, *oAlp; + + iRed = *( jpegline + count * 4 + 0 ); + iGrn = *( jpegline + count * 4 + 1 ); + iBlu = *( jpegline + count * 4 + 2 ); + /* iAlp = *( jpegline + count * 4 + 3 ); */ + + oRed = outBuf + offset + count * 4 + 0; + oGrn = outBuf + offset + count * 4 + 1; + oBlu = outBuf + offset + count * 4 + 2; + oAlp = outBuf + offset + count * 4 + 3; + + *oRed = iRed; + *oGrn = iGrn; + *oBlu = iBlu; + // ydnar: see bug 900 + *oAlp = 255; //% iAlp; + } +} + +// stash a gray scanline +static void j_putGrayScanlineToRGB( unsigned char* jpegline, int widthPix, unsigned char* outBuf, int row ){ + int offset = row * widthPix * 4; + int count; + + for ( count = 0; count < widthPix; count++ ) + { + unsigned char iGray; + unsigned char *oRed, *oBlu, *oGrn, *oAlp; + + // get our grayscale value + iGray = *( jpegline + count ); + + oRed = outBuf + offset + count * 4; + oGrn = outBuf + offset + count * 4 + 1; + oBlu = outBuf + offset + count * 4 + 2; + oAlp = outBuf + offset + count * 4 + 3; + + *oRed = iGray; + *oGrn = iGray; + *oBlu = iGray; + *oAlp = 255; + } +} + +int LoadJPGBuff( void *src_buffer, int src_size, unsigned char **pic, int *width, int *height ) { + struct jpeg_decompress_struct cinfo; + struct my_jpeg_error_mgr jerr; + JSAMPARRAY buffer; + int row_stride, size; + + cinfo.err = jpeg_std_error( &jerr.pub ); + jerr.pub.error_exit = my_jpeg_error_exit; + + if ( setjmp( jerr.setjmp_buffer ) ) { + *pic = (unsigned char*)errormsg; + jpeg_destroy_decompress( &cinfo ); + return -1; + } + + jpeg_create_decompress( &cinfo ); + jpeg_buffer_src( &cinfo, src_buffer, src_size ); + jpeg_read_header( &cinfo, TRUE ); + jpeg_start_decompress( &cinfo ); + + row_stride = cinfo.output_width * cinfo.output_components; + + size = cinfo.output_width * cinfo.output_height * 4; + *width = cinfo.output_width; + *height = cinfo.output_height; + *pic = (unsigned char*)( malloc( size + 1 ) ); + memset( *pic, 0, size + 1 ); + + buffer = ( *cinfo.mem->alloc_sarray )( ( j_common_ptr ) & cinfo, JPOOL_IMAGE, row_stride, 1 ); + + while ( cinfo.output_scanline < cinfo.output_height ) + { + jpeg_read_scanlines( &cinfo, buffer, 1 ); + + if ( cinfo.out_color_components == 4 ) { + j_putRGBAScanline( buffer[0], cinfo.output_width, *pic, cinfo.output_scanline - 1 ); + } + else if ( cinfo.out_color_components == 3 ) { + j_putRGBScanline( buffer[0], cinfo.output_width, *pic, cinfo.output_scanline - 1 ); + } + else if ( cinfo.out_color_components == 1 ) { + j_putGrayScanlineToRGB( buffer[0], cinfo.output_width, *pic, cinfo.output_scanline - 1 ); + } + } + + jpeg_finish_decompress( &cinfo ); + jpeg_destroy_decompress( &cinfo ); + + return 0; +} diff --git a/tools/common/l3dslib.c b/tools/common/l3dslib.c new file mode 100644 index 0000000..5e340b9 --- /dev/null +++ b/tools/common/l3dslib.c @@ -0,0 +1,312 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// +// l3dslib.c: library for loading triangles from an Alias triangle file +// + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "trilib.h" +#include "l3dslib.h" + +#define MAIN3DS 0x4D4D +#define EDIT3DS 0x3D3D // this is the start of the editor config +#define EDIT_OBJECT 0x4000 +#define OBJ_TRIMESH 0x4100 +#define TRI_VERTEXL 0x4110 +#define TRI_FACEL1 0x4120 + +#define MAXVERTS 2000 +#define MAXTRIANGLES 750 + +typedef struct { + int v[4]; +} tri; + +float fverts[MAXVERTS][3]; +tri tris[MAXTRIANGLES]; + +int bytesread, level, numtris, totaltris; +int vertsfound, trisfound; + +triangle_t *ptri; + + +// Alias stores triangles as 3 explicit vertices in .tri files, so even though we +// start out with a vertex pool and vertex indices for triangles, we have to convert +// to raw, explicit triangles +void StoreAliasTriangles( void ){ + int i, j, k; + + if ( ( totaltris + numtris ) > MAXTRIANGLES ) { + Error( "Error: Too many triangles" ); + } + + for ( i = 0; i < numtris ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + for ( k = 0 ; k < 3 ; k++ ) + { + ptri[i + totaltris].verts[j][k] = fverts[tris[i].v[j]][k]; + } + } + } + + totaltris += numtris; + numtris = 0; + vertsfound = 0; + trisfound = 0; +} + + +int ParseVertexL( FILE *input ){ + int i, j, startbytesread, numverts; + unsigned short tshort; + + if ( vertsfound ) { + Error( "Error: Multiple vertex chunks" ); + } + + vertsfound = 1; + startbytesread = bytesread; + + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &tshort, sizeof( tshort ), 1, input ); + bytesread += sizeof( tshort ); + numverts = (int)tshort; + + if ( numverts > MAXVERTS ) { + Error( "Error: Too many vertices" ); + } + + for ( i = 0 ; i < numverts ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &fverts[i][j], sizeof( float ), 1, input ); + bytesread += sizeof( float ); + } + } + + if ( vertsfound && trisfound ) { + StoreAliasTriangles(); + } + + return bytesread - startbytesread; +} + + +int ParseFaceL1( FILE *input ){ + + int i, j, startbytesread; + unsigned short tshort; + + if ( trisfound ) { + Error( "Error: Multiple face chunks" ); + } + + trisfound = 1; + startbytesread = bytesread; + + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &tshort, sizeof( tshort ), 1, input ); + bytesread += sizeof( tshort ); + numtris = (int)tshort; + + if ( numtris > MAXTRIANGLES ) { + Error( "Error: Too many triangles" ); + } + + for ( i = 0 ; i < numtris ; i++ ) + { + for ( j = 0 ; j < 4 ; j++ ) + { + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &tshort, sizeof( tshort ), 1, input ); + bytesread += sizeof( tshort ); + tris[i].v[j] = (int)tshort; + } + } + + if ( vertsfound && trisfound ) { + StoreAliasTriangles(); + } + + return bytesread - startbytesread; +} + + +int ParseChunk( FILE *input ){ +#define BLOCK_SIZE 4096 + char temp[BLOCK_SIZE]; + unsigned short type; + int i, length, w, t, retval; + + level++; + retval = 0; + +// chunk type + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &type, sizeof( type ), 1, input ); + bytesread += sizeof( type ); + +// chunk length + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &length, sizeof( length ), 1, input ); + bytesread += sizeof( length ); + w = length - 6; + +// process chunk if we care about it, otherwise skip it + switch ( type ) + { + case TRI_VERTEXL: + w -= ParseVertexL( input ); + goto ParseSubchunk; + + case TRI_FACEL1: + w -= ParseFaceL1( input ); + goto ParseSubchunk; + + case EDIT_OBJECT: + // read the name + i = 0; + + do + { + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &temp[i], 1, 1, input ); + i++; + w--; + bytesread++; + } while ( temp[i - 1] ); + + case MAIN3DS: + case OBJ_TRIMESH: + case EDIT3DS: + // parse through subchunks +ParseSubchunk: + while ( w > 0 ) + { + w -= ParseChunk( input ); + } + + retval = length; + goto Done; + + default: + // skip other chunks + while ( w > 0 ) + { + t = w; + + if ( t > BLOCK_SIZE ) { + t = BLOCK_SIZE; + } + + if ( feof( input ) ) { + Error( "Error: unexpected end of file" ); + } + + fread( &temp, t, 1, input ); + bytesread += t; + + w -= t; + } + + retval = length; + goto Done; + } + +Done: + level--; + return retval; +} + + +void Load3DSTriangleList( char *filename, triangle_t **pptri, int *numtriangles ){ + FILE *input; + short int tshort; + + bytesread = 0; + level = 0; + numtris = 0; + totaltris = 0; + vertsfound = 0; + trisfound = 0; + + if ( ( input = fopen( filename, "rb" ) ) == 0 ) { + fprintf( stderr,"reader: could not open file '%s'\n", filename ); + exit( 0 ); + } + + fread( &tshort, sizeof( tshort ), 1, input ); + +// should only be MAIN3DS, but some files seem to start with EDIT3DS, with +// no MAIN3DS + if ( ( tshort != MAIN3DS ) && ( tshort != EDIT3DS ) ) { + fprintf( stderr,"File is not a 3DS file.\n" ); + exit( 0 ); + } + +// back to top of file so we can parse the first chunk descriptor + fseek( input, 0, SEEK_SET ); + + ptri = safe_malloc( MAXTRIANGLES * sizeof( triangle_t ) ); + + *pptri = ptri; + +// parse through looking for the relevant chunk tree (MAIN3DS | EDIT3DS | EDIT_OBJECT | +// OBJ_TRIMESH | {TRI_VERTEXL, TRI_FACEL1}) and skipping other chunks + ParseChunk( input ); + + if ( vertsfound || trisfound ) { + Error( "Incomplete triangle set" ); + } + + *numtriangles = totaltris; + + fclose( input ); +} diff --git a/tools/common/l3dslib.h b/tools/common/l3dslib.h new file mode 100644 index 0000000..98ed502 --- /dev/null +++ b/tools/common/l3dslib.h @@ -0,0 +1,25 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// +// l3dslib.h: header file for loading triangles from a 3DS triangle file +// +void Load3DSTriangleList( char *filename, triangle_t **pptri, int *numtriangles ); diff --git a/tools/common/md4.c b/tools/common/md4.c new file mode 100644 index 0000000..f503422 --- /dev/null +++ b/tools/common/md4.c @@ -0,0 +1,219 @@ +/* + mdfour.c + + An implementation of MD4 designed for use in the samba SMB + authentication protocol + + Copyright (C) 1997-1998 Andrew Tridgell + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + + $Id: mdfour.c 7689 2007-11-12 14:28:40Z divverent $ + */ + +#include /* XoXus: needed for memset call */ +#include "md4.h" + +/* NOTE: This code makes no attempt to be fast! + + It assumes that a int is at least 32 bits long + */ + +static struct mdfour *m; + +#define F( X,Y,Z ) ( ( (X)&( Y ) ) | ( ( ~( X ) ) & ( Z ) ) ) +#define G( X,Y,Z ) ( ( (X)&( Y ) ) | ( (X)&( Z ) ) | ( (Y)&( Z ) ) ) +#define H( X,Y,Z ) ( ( X ) ^ ( Y ) ^ ( Z ) ) +#ifdef LARGE_INT32 +#define lshift( x,s ) ( ( ( ( x ) << ( s ) ) & 0xFFFFFFFF ) | ( ( ( x ) >> ( 32 - ( s ) ) ) & 0xFFFFFFFF ) ) +#else +#define lshift( x,s ) ( ( ( x ) << ( s ) ) | ( ( x ) >> ( 32 - ( s ) ) ) ) +#endif + +#define ROUND1( a,b,c,d,k,s ) a = lshift( a + F( b,c,d ) + X[k], s ) +#define ROUND2( a,b,c,d,k,s ) a = lshift( a + G( b,c,d ) + X[k] + 0x5A827999,s ) +#define ROUND3( a,b,c,d,k,s ) a = lshift( a + H( b,c,d ) + X[k] + 0x6ED9EBA1,s ) + +/* this applies md4 to 64 byte chunks */ +static void mdfour64( uint32 *M ){ + int j; + uint32 AA, BB, CC, DD; + uint32 X[16]; + uint32 A,B,C,D; + + for ( j = 0; j < 16; j++ ) + X[j] = M[j]; + + A = m->A; B = m->B; C = m->C; D = m->D; + AA = A; BB = B; CC = C; DD = D; + + ROUND1( A,B,C,D, 0, 3 ); ROUND1( D,A,B,C, 1, 7 ); + ROUND1( C,D,A,B, 2, 11 ); ROUND1( B,C,D,A, 3, 19 ); + ROUND1( A,B,C,D, 4, 3 ); ROUND1( D,A,B,C, 5, 7 ); + ROUND1( C,D,A,B, 6, 11 ); ROUND1( B,C,D,A, 7, 19 ); + ROUND1( A,B,C,D, 8, 3 ); ROUND1( D,A,B,C, 9, 7 ); + ROUND1( C,D,A,B, 10, 11 ); ROUND1( B,C,D,A, 11, 19 ); + ROUND1( A,B,C,D, 12, 3 ); ROUND1( D,A,B,C, 13, 7 ); + ROUND1( C,D,A,B, 14, 11 ); ROUND1( B,C,D,A, 15, 19 ); + + ROUND2( A,B,C,D, 0, 3 ); ROUND2( D,A,B,C, 4, 5 ); + ROUND2( C,D,A,B, 8, 9 ); ROUND2( B,C,D,A, 12, 13 ); + ROUND2( A,B,C,D, 1, 3 ); ROUND2( D,A,B,C, 5, 5 ); + ROUND2( C,D,A,B, 9, 9 ); ROUND2( B,C,D,A, 13, 13 ); + ROUND2( A,B,C,D, 2, 3 ); ROUND2( D,A,B,C, 6, 5 ); + ROUND2( C,D,A,B, 10, 9 ); ROUND2( B,C,D,A, 14, 13 ); + ROUND2( A,B,C,D, 3, 3 ); ROUND2( D,A,B,C, 7, 5 ); + ROUND2( C,D,A,B, 11, 9 ); ROUND2( B,C,D,A, 15, 13 ); + + ROUND3( A,B,C,D, 0, 3 ); ROUND3( D,A,B,C, 8, 9 ); + ROUND3( C,D,A,B, 4, 11 ); ROUND3( B,C,D,A, 12, 15 ); + ROUND3( A,B,C,D, 2, 3 ); ROUND3( D,A,B,C, 10, 9 ); + ROUND3( C,D,A,B, 6, 11 ); ROUND3( B,C,D,A, 14, 15 ); + ROUND3( A,B,C,D, 1, 3 ); ROUND3( D,A,B,C, 9, 9 ); + ROUND3( C,D,A,B, 5, 11 ); ROUND3( B,C,D,A, 13, 15 ); + ROUND3( A,B,C,D, 3, 3 ); ROUND3( D,A,B,C, 11, 9 ); + ROUND3( C,D,A,B, 7, 11 ); ROUND3( B,C,D,A, 15, 15 ); + + A += AA; B += BB; C += CC; D += DD; + +#ifdef LARGE_INT32 + A &= 0xFFFFFFFF; B &= 0xFFFFFFFF; + C &= 0xFFFFFFFF; D &= 0xFFFFFFFF; +#endif + + for ( j = 0; j < 16; j++ ) + X[j] = 0; + + m->A = A; m->B = B; m->C = C; m->D = D; +} + +static void copy64( uint32 *M, unsigned char *in ){ + int i; + + for ( i = 0; i < 16; i++ ) + M[i] = ( in[i * 4 + 3] << 24 ) | ( in[i * 4 + 2] << 16 ) | + ( in[i * 4 + 1] << 8 ) | ( in[i * 4 + 0] << 0 ); +} + +static void copy4( unsigned char *out,uint32 x ){ + out[0] = x & 0xFF; + out[1] = ( x >> 8 ) & 0xFF; + out[2] = ( x >> 16 ) & 0xFF; + out[3] = ( x >> 24 ) & 0xFF; +} + +void mdfour_begin( struct mdfour *md ){ + md->A = 0x67452301; + md->B = 0xefcdab89; + md->C = 0x98badcfe; + md->D = 0x10325476; + md->totalN = 0; +} + + +static void mdfour_tail( unsigned char *in, int n ){ + unsigned char buf[128]; + uint32 M[16]; + uint32 b; + + m->totalN += n; + + b = m->totalN * 8; + + memset( buf, 0, 128 ); + if ( n ) { + memcpy( buf, in, n ); + } + buf[n] = 0x80; + + if ( n <= 55 ) { + copy4( buf + 56, b ); + copy64( M, buf ); + mdfour64( M ); + } + else { + copy4( buf + 120, b ); + copy64( M, buf ); + mdfour64( M ); + copy64( M, buf + 64 ); + mdfour64( M ); + } +} + +void mdfour_update( struct mdfour *md, unsigned char *in, int n ){ + uint32 M[16]; + +// start of edit by Forest 'LordHavoc' Hale +// commented out to prevent crashing when length is 0 +// if (n == 0) mdfour_tail(in, n); +// end of edit by Forest 'LordHavoc' Hale + + m = md; + + while ( n >= 64 ) { + copy64( M, in ); + mdfour64( M ); + in += 64; + n -= 64; + m->totalN += 64; + } + + mdfour_tail( in, n ); +} + + +void mdfour_result( struct mdfour *md, unsigned char *out ){ + m = md; + + copy4( out, m->A ); + copy4( out + 4, m->B ); + copy4( out + 8, m->C ); + copy4( out + 12, m->D ); +} + + +void mdfour( unsigned char *out, unsigned char *in, int n ){ + struct mdfour md; + mdfour_begin( &md ); + mdfour_update( &md, in, n ); + mdfour_result( &md, out ); +} + +/////////////////////////////////////////////////////////////// +// MD4-based checksum utility functions +// +// Copyright (C) 2000 Jeff Teunissen +// +// Author: Jeff Teunissen +// Date: 01 Jan 2000 + +unsigned Com_BlockChecksum( void *buffer, int length ){ + int digest[4]; + unsigned val; + + mdfour( (unsigned char *) digest, (unsigned char *) buffer, length ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} + +void Com_BlockFullChecksum( void *buffer, int len, unsigned char *outbuf ){ + mdfour( outbuf, (unsigned char *) buffer, len ); +} diff --git a/tools/common/md4.h b/tools/common/md4.h new file mode 100644 index 0000000..6f64c28 --- /dev/null +++ b/tools/common/md4.h @@ -0,0 +1,56 @@ +/* + mdfour.h + + an implementation of MD4 designed for use in the SMB authentication + protocol + + Copyright (C) Andrew Tridgell 1997-1998 + + 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: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + */ + +#ifndef _MDFOUR_H +#define _MDFOUR_H + +#ifndef int32 +#define int32 int +#endif + +#if SIZEOF_INT > 4 +#define LARGE_INT32 +#endif + +#ifndef uint32 +#define uint32 unsigned int32 +#endif + +struct mdfour { + uint32 A, B, C, D; + uint32 totalN; +}; + +void mdfour_begin( struct mdfour *md ); // old: MD4Init +void mdfour_update( struct mdfour *md, unsigned char *in, int n ); //old: MD4Update +void mdfour_result( struct mdfour *md, unsigned char *out ); // old: MD4Final +void mdfour( unsigned char *out, unsigned char *in, int n ); + +unsigned Com_BlockChecksum( void *buffer, int length ); +void Com_BlockFullChecksum( void *buffer, int len, unsigned char *outbuf ); + +#endif // _MDFOUR_H diff --git a/tools/common/mutex.c b/tools/common/mutex.c new file mode 100644 index 0000000..d442267 --- /dev/null +++ b/tools/common/mutex.c @@ -0,0 +1,197 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "globaldefs.h" +#include "cmdlib.h" +#include "qthreads.h" +#include "mutex.h" + +/* + =================================================================== + + WIN32 + + =================================================================== + */ +#if GDEF_OS_WINDOWS + +#define USED + +#include + +void MutexLock( mutex_t *m ){ + CRITICAL_SECTION *crit; + + if ( !m ) { + return; + } + crit = (CRITICAL_SECTION *) m; + EnterCriticalSection( crit ); +} + +void MutexUnlock( mutex_t *m ){ + CRITICAL_SECTION *crit; + + if ( !m ) { + return; + } + crit = (CRITICAL_SECTION *) m; + LeaveCriticalSection( crit ); +} + +mutex_t *MutexAlloc( void ){ + CRITICAL_SECTION *crit; + + if ( numthreads == 1 ) { + return NULL; + } + crit = (CRITICAL_SECTION *) safe_malloc( sizeof( CRITICAL_SECTION ) ); + InitializeCriticalSection( crit ); + return (void *) crit; +} + +#endif + +/* + =================================================================== + + OSF1 + + =================================================================== + */ + +#ifdef __osf__ +#define USED + +#include + +void MutexLock( mutex_t *m ){ + pthread_mutex_t *my_mutex; + + if ( !m ) { + return; + } + my_mutex = (pthread_mutex_t *) m; + pthread_mutex_lock( my_mutex ); +} + +void MutexUnlock( mutex_t *m ){ + pthread_mutex_t *my_mutex; + + if ( !m ) { + return; + } + my_mutex = (pthread_mutex_t *) m; + pthread_mutex_unlock( my_mutex ); +} + +mutex_t *MutexAlloc( void ){ + pthread_mutex_t *my_mutex; + pthread_mutexattr_t mattrib; + + if ( numthreads == 1 ) { + return NULL; + } + my_mutex = safe_malloc( sizeof( *my_mutex ) ); + if ( pthread_mutexattr_create( &mattrib ) == -1 ) { + Error( "pthread_mutex_attr_create failed" ); + } + if ( pthread_mutexattr_setkind_np( &mattrib, MUTEX_FAST_NP ) == -1 ) { + Error( "pthread_mutexattr_setkind_np failed" ); + } + if ( pthread_mutex_init( my_mutex, mattrib ) == -1 ) { + Error( "pthread_mutex_init failed" ); + } + return (void *) my_mutex; +} + +#endif + +/* + =================================================================== + + IRIX + + =================================================================== + */ + +#ifdef _MIPS_ISA +#define USED + +#include +#include +#include +#include + +void MutexLock( mutex_t *m ){ + abilock_t *lck; + + if ( !m ) { + return; + } + lck = (abilock_t *) m; + spin_lock( lck ); +} + +void MutexUnlock( mutex_t *m ){ + abilock_t *lck; + + if ( !m ) { + return; + } + lck = (abilock_t *) m; + release_lock( lck ); +} + +mutex_t *MutexAlloc( void ){ + abilock_t *lck; + + if ( numthreads == 1 ) { + return NULL; + } + lck = (abilock_t *) safe_malloc( sizeof( abilock_t ) ); + init_lock( lck ); + return (void *) lck; +} + +#endif + +/* + ======================================================================= + + SINGLE THREAD + + ======================================================================= + */ + +#ifndef USED + +void MutexLock( mutex_t *m ){ +} + +void MutexUnlock( mutex_t *m ){ +} + +mutex_t *MutexAlloc( void ){ + return NULL; +} + +#endif diff --git a/tools/common/mutex.h b/tools/common/mutex.h new file mode 100644 index 0000000..cbc23f6 --- /dev/null +++ b/tools/common/mutex.h @@ -0,0 +1,28 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + + +typedef void *mutex_t; + +void MutexLock( mutex_t *m ); +void MutexUnlock( mutex_t *m ); +mutex_t *MutexAlloc( void ); diff --git a/tools/common/polylib.c b/tools/common/polylib.c new file mode 100644 index 0000000..bef8fbd --- /dev/null +++ b/tools/common/polylib.c @@ -0,0 +1,1147 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "polylib.h" +#include "qfiles.h" + + +extern int numthreads; + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +#define BOGUS_RANGE WORLD_SIZE + +void pw( winding_t *w ){ + int i; + for ( i = 0 ; i < w->numpoints ; i++ ) + Sys_Printf( "(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2] ); +} + + +/* + ============= + AllocWinding + ============= + */ +winding_t *AllocWinding( int points ){ + winding_t *w; + int s; + + if ( points >= MAX_POINTS_ON_WINDING ) { + Error( "AllocWinding failed: MAX_POINTS_ON_WINDING exceeded" ); + } + + if ( numthreads == 1 ) { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if ( c_active_windings > c_peak_windings ) { + c_peak_windings = c_active_windings; + } + } + s = sizeof( *w ) + ( points ? sizeof( w->p[0] ) * ( points - 1 ) : 0 ); + w = safe_malloc( s ); + memset( w, 0, s ); + return w; +} + +/* + ============= + AllocWindingAccu + ============= + */ +winding_accu_t *AllocWindingAccu( int points ){ + winding_accu_t *w; + int s; + + if ( points >= MAX_POINTS_ON_WINDING ) { + Error( "AllocWindingAccu failed: MAX_POINTS_ON_WINDING exceeded" ); + } + + if ( numthreads == 1 ) { + // At the time of this writing, these statistics were not used in any way. + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if ( c_active_windings > c_peak_windings ) { + c_peak_windings = c_active_windings; + } + } + s = sizeof( *w ) + ( points ? sizeof( w->p[0] ) * ( points - 1 ) : 0 ); + w = safe_malloc( s ); + memset( w, 0, s ); + return w; +} + +/* + ============= + FreeWinding + ============= + */ +void FreeWinding( winding_t *w ){ + if ( !w ) { + Error( "FreeWinding: winding is NULL" ); + } + + if ( *(unsigned *)w == 0xdeaddead ) { + Error( "FreeWinding: freed a freed winding" ); + } + *(unsigned *)w = 0xdeaddead; + + if ( numthreads == 1 ) { + c_active_windings--; + } + free( w ); +} + +/* + ============= + FreeWindingAccu + ============= + */ +void FreeWindingAccu( winding_accu_t *w ){ + if ( !w ) { + Error( "FreeWindingAccu: winding is NULL" ); + } + + if ( *( (unsigned *) w ) == 0xdeaddead ) { + Error( "FreeWindingAccu: freed a freed winding" ); + } + *( (unsigned *) w ) = 0xdeaddead; + + if ( numthreads == 1 ) { + c_active_windings--; + } + free( w ); +} + +/* + ============ + RemoveColinearPoints + ============ + */ +int c_removed; + +void RemoveColinearPoints( winding_t *w ){ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + j = ( i + 1 ) % w->numpoints; + k = ( i + w->numpoints - 1 ) % w->numpoints; + VectorSubtract( w->p[j], w->p[i], v1 ); + VectorSubtract( w->p[i], w->p[k], v2 ); + VectorNormalize( v1,v1 ); + VectorNormalize( v2,v2 ); + if ( DotProduct( v1, v2 ) < 0.999 ) { + VectorCopy( w->p[i], p[nump] ); + nump++; + } + } + + if ( nump == w->numpoints ) { + return; + } + + if ( numthreads == 1 ) { + c_removed += w->numpoints - nump; + } + w->numpoints = nump; + memcpy( w->p, p, nump * sizeof( p[0] ) ); +} + +/* + ============ + WindingPlane + ============ + */ +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ){ + vec3_t v1, v2; + + VectorSubtract( w->p[1], w->p[0], v1 ); + VectorSubtract( w->p[2], w->p[0], v2 ); + CrossProduct( v2, v1, normal ); + VectorNormalize( normal, normal ); + *dist = DotProduct( w->p[0], normal ); + +} + +/* + ============= + WindingArea + ============= + */ +vec_t WindingArea( winding_t *w ){ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for ( i = 2 ; i < w->numpoints ; i++ ) + { + VectorSubtract( w->p[i - 1], w->p[0], d1 ); + VectorSubtract( w->p[i], w->p[0], d2 ); + CrossProduct( d1, d2, cross ); + total += 0.5 * VectorLength( cross ); + } + return total; +} + +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs ){ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + { + v = w->p[i][j]; + if ( v < mins[j] ) { + mins[j] = v; + } + if ( v > maxs[j] ) { + maxs[j] = v; + } + } + } +} + +/* + ============= + WindingCenter + ============= + */ +void WindingCenter( winding_t *w, vec3_t center ){ + int i; + float scale; + + VectorCopy( vec3_origin, center ); + for ( i = 0 ; i < w->numpoints ; i++ ) + VectorAdd( w->p[i], center, center ); + + scale = 1.0 / w->numpoints; + VectorScale( center, scale, center ); +} + +/* + ================= + BaseWindingForPlaneAccu + ================= + */ +winding_accu_t *BaseWindingForPlaneAccu( vec3_t normal, vec_t dist ){ + // The goal in this function is to replicate the behavior of the original BaseWindingForPlane() + // function (see below) but at the same time increasing accuracy substantially. + + // The original code gave a preference for the vup vector to start out as (0, 0, 1), unless the + // normal had a dominant Z value, in which case vup started out as (1, 0, 0). After that, vup + // was "bent" [along the plane defined by normal and vup] to become perpendicular to normal. + // After that the vright vector was computed as the cross product of vup and normal. + + // I'm constructing the winding polygon points in a fashion similar to the method used in the + // original function. Orientation is the same. The size of the winding polygon, however, is + // variable in this function (depending on the angle of normal), and is larger (by about a factor + // of 2) than the winding polygon in the original function. + + int x, i; + vec_t max, v; + vec3_accu_t vright, vup, org, normalAccu; + winding_accu_t *w; + + // One of the components of normal must have a magnitiude greater than this value, + // otherwise normal is not a unit vector. This is a little bit of inexpensive + // partial error checking we can do. + max = 0.56; // 1 / sqrt(1^2 + 1^2 + 1^2) = 0.577350269 + + x = -1; + for ( i = 0; i < 3; i++ ) { + v = (vec_t) fabs( normal[i] ); + if ( v > max ) { + x = i; + max = v; + } + } + if ( x == -1 ) { + Error( "BaseWindingForPlaneAccu: no dominant axis found because normal is too short" ); + } + + switch ( x ) { + case 0: // Fall through to next case. + case 1: + vright[0] = (vec_accu_t) -normal[1]; + vright[1] = (vec_accu_t) normal[0]; + vright[2] = 0; + break; + case 2: + vright[0] = 0; + vright[1] = (vec_accu_t) -normal[2]; + vright[2] = (vec_accu_t) normal[1]; + break; + } + + // vright and normal are now perpendicular; you can prove this by taking their + // dot product and seeing that it's always exactly 0 (with no error). + + // NOTE: vright is NOT a unit vector at this point. vright will have length + // not exceeding 1.0. The minimum length that vright can achieve happens when, + // for example, the Z and X components of the normal input vector are equal, + // and when normal's Y component is zero. In that case Z and X of the normal + // vector are both approximately 0.70711. The resulting vright vector in this + // case will have a length of 0.70711. + + // We're relying on the fact that MAX_WORLD_COORD is a power of 2 to keep + // our calculation precise and relatively free of floating point error. + // [However, the code will still work fine if that's not the case.] + VectorScaleAccu( vright, ( (vec_accu_t) MAX_WORLD_COORD ) * 4.0, vright ); + + // At time time of this writing, MAX_WORLD_COORD was 65536 (2^16). Therefore + // the length of vright at this point is at least 185364. In comparison, a + // corner of the world at location (65536, 65536, 65536) is distance 113512 + // away from the origin. + + VectorCopyRegularToAccu( normal, normalAccu ); + CrossProductAccu( normalAccu, vright, vup ); + + // vup now has length equal to that of vright. + + VectorScaleAccu( normalAccu, (vec_accu_t) dist, org ); + + // org is now a point on the plane defined by normal and dist. Furthermore, + // org, vright, and vup are pairwise perpendicular. Now, the 4 vectors + // { (+-)vright + (+-)vup } have length that is at least sqrt(185364^2 + 185364^2), + // which is about 262144. That length lies outside the world, since the furthest + // point in the world has distance 113512 from the origin as mentioned above. + // Also, these 4 vectors are perpendicular to the org vector. So adding them + // to org will only increase their length. Therefore the 4 points defined below + // all lie outside of the world. Furthermore, it can be easily seen that the + // edges connecting these 4 points (in the winding_accu_t below) lie completely + // outside the world. sqrt(262144^2 + 262144^2)/2 = 185363, which is greater than + // 113512. + + w = AllocWindingAccu( 4 ); + + VectorSubtractAccu( org, vright, w->p[0] ); + VectorAddAccu( w->p[0], vup, w->p[0] ); + + VectorAddAccu( org, vright, w->p[1] ); + VectorAddAccu( w->p[1], vup, w->p[1] ); + + VectorAddAccu( org, vright, w->p[2] ); + VectorSubtractAccu( w->p[2], vup, w->p[2] ); + + VectorSubtractAccu( org, vright, w->p[3] ); + VectorSubtractAccu( w->p[3], vup, w->p[3] ); + + w->numpoints = 4; + + return w; +} + +/* + ================= + BaseWindingForPlane + + Original BaseWindingForPlane() function that has serious accuracy problems. Here is why. + The base winding is computed as a rectangle with very large coordinates. These coordinates + are in the range 2^17 or 2^18. "Epsilon" (meaning the distance between two adjacent numbers) + at these magnitudes in 32 bit floating point world is about 0.02. So for example, if things + go badly (by bad luck), then the whole plane could be shifted by 0.02 units (its distance could + be off by that much). Then if we were to compute the winding of this plane and another of + the brush's planes met this winding at a very acute angle, that error could multiply to around + 0.1 or more when computing the final vertex coordinates of the winding. 0.1 is a very large + error, and can lead to all sorts of disappearing triangle problems. + ================= + */ +winding_t *BaseWindingForPlane( vec3_t normal, vec_t dist ){ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for ( i = 0 ; i < 3; i++ ) + { + v = fabs( normal[i] ); + if ( v > max ) { + x = i; + max = v; + } + } + if ( x == -1 ) { + Error( "BaseWindingForPlane: no axis found" ); + } + + VectorCopy( vec3_origin, vup ); + switch ( x ) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct( vup, normal ); + VectorMA( vup, -v, normal, vup ); + VectorNormalize( vup, vup ); + + VectorScale( normal, dist, org ); + + CrossProduct( vup, normal, vright ); + + // LordHavoc: this has to use *2 because otherwise some created points may + // be inside the world (think of a diagonal case), and any brush with such + // points should be removed, failure to detect such cases is disasterous + VectorScale( vup, MAX_WORLD_COORD * 2, vup ); + VectorScale( vright, MAX_WORLD_COORD * 2, vright ); + + // project a really big axis aligned box onto the plane + w = AllocWinding( 4 ); + + VectorSubtract( org, vright, w->p[0] ); + VectorAdd( w->p[0], vup, w->p[0] ); + + VectorAdd( org, vright, w->p[1] ); + VectorAdd( w->p[1], vup, w->p[1] ); + + VectorAdd( org, vright, w->p[2] ); + VectorSubtract( w->p[2], vup, w->p[2] ); + + VectorSubtract( org, vright, w->p[3] ); + VectorSubtract( w->p[3], vup, w->p[3] ); + + w->numpoints = 4; + + return w; +} + +/* + ================== + CopyWinding + ================== + */ +winding_t *CopyWinding( winding_t *w ){ + size_t size; + winding_t *c; + + if ( !w ) { + Error( "CopyWinding: winding is NULL" ); + } + + c = AllocWinding( w->numpoints ); + size = (size_t)( (winding_t *)NULL )->p[w->numpoints]; + memcpy( c, w, size ); + return c; +} + +/* + ================== + CopyWindingAccuIncreaseSizeAndFreeOld + ================== + */ +winding_accu_t *CopyWindingAccuIncreaseSizeAndFreeOld( winding_accu_t *w ){ + int i; + winding_accu_t *c; + + if ( !w ) { + Error( "CopyWindingAccuIncreaseSizeAndFreeOld: winding is NULL" ); + } + + c = AllocWindingAccu( w->numpoints + 1 ); + c->numpoints = w->numpoints; + for ( i = 0; i < c->numpoints; i++ ) + { + VectorCopyAccu( w->p[i], c->p[i] ); + } + FreeWindingAccu( w ); + return c; +} + +/* + ================== + CopyWindingAccuToRegular + ================== + */ +winding_t *CopyWindingAccuToRegular( winding_accu_t *w ){ + int i; + winding_t *c; + + if ( !w ) { + Error( "CopyWindingAccuToRegular: winding is NULL" ); + } + + c = AllocWinding( w->numpoints ); + c->numpoints = w->numpoints; + for ( i = 0; i < c->numpoints; i++ ) + { + VectorCopyAccuToRegular( w->p[i], c->p[i] ); + } + return c; +} + +/* + ================== + ReverseWinding + ================== + */ +winding_t *ReverseWinding( winding_t *w ){ + int i; + winding_t *c; + + c = AllocWinding( w->numpoints ); + for ( i = 0 ; i < w->numpoints ; i++ ) + { + VectorCopy( w->p[w->numpoints - 1 - i], c->p[i] ); + } + c->numpoints = w->numpoints; + return c; +} + + +/* + ============= + ClipWindingEpsilon + ============= + */ +void ClipWindingEpsilonStrict( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ){ + vec_t dists[MAX_POINTS_ON_WINDING + 4]; + int sides[MAX_POINTS_ON_WINDING + 4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for ( i = 0 ; i < in->numpoints ; i++ ) + { + + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if ( !counts[0] && !counts[1] ) { + return; + } + if ( !counts[0] ) { + *back = CopyWinding( in ); + return; + } + if ( !counts[1] ) { + *front = CopyWinding( in ); + return; + } + + maxpts = in->numpoints + 4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding( maxpts ); + *back = b = AllocWinding( maxpts ); + + for ( i = 0 ; i < in->numpoints ; i++ ) + { + p1 = in->p[i]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + if ( sides[i] == SIDE_BACK ) { + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + } + + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = in->p[( i + 1 ) % in->numpoints]; + + dot = dists[i] / ( dists[i] - dists[i + 1] ); + for ( j = 0 ; j < 3 ; j++ ) + { // avoid round off error when possible + if ( normal[j] == 1 ) { + mid[j] = dist; + } + else if ( normal[j] == -1 ) { + mid[j] = -dist; + } + else{ + mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + } + + VectorCopy( mid, f->p[f->numpoints] ); + f->numpoints++; + VectorCopy( mid, b->p[b->numpoints] ); + b->numpoints++; + } + + if ( f->numpoints > maxpts || b->numpoints > maxpts ) { + Error( "ClipWinding: points exceeded estimate" ); + } + if ( f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING ) { + Error( "ClipWinding: MAX_POINTS_ON_WINDING" ); + } +} + +void ClipWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ){ + ClipWindingEpsilonStrict( in, normal, dist, epsilon, front, back ); + /* apparently most code expects that in the winding-on-plane case, the back winding is the original winding */ + if ( !*front && !*back ) { + *back = CopyWinding( in ); + } +} + + +/* + ============= + ChopWindingInPlaceAccu + ============= + */ +void ChopWindingInPlaceAccu( winding_accu_t **inout, vec3_t normal, vec_t dist, vec_t crudeEpsilon ){ + vec_accu_t fineEpsilon; + winding_accu_t *in; + int counts[3]; + int i, j; + vec_accu_t dists[MAX_POINTS_ON_WINDING + 1]; + int sides[MAX_POINTS_ON_WINDING + 1]; + int maxpts; + winding_accu_t *f; + vec_accu_t *p1, *p2; + vec_accu_t w; + vec3_accu_t mid, normalAccu; + + // We require at least a very small epsilon. It's a good idea for several reasons. + // First, we will be dividing by a potentially very small distance below. We don't + // want that distance to be too small; otherwise, things "blow up" with little accuracy + // due to the division. (After a second look, the value w below is in range (0,1), but + // graininess problem remains.) Second, Having minimum epsilon also prevents the following + // situation. Say for example we have a perfect octagon defined by the input winding. + // Say our chopping plane (defined by normal and dist) is essentially the same plane + // that the octagon is sitting on. Well, due to rounding errors, it may be that point + // 1 of the octagon might be in front, point 2 might be in back, point 3 might be in + // front, point 4 might be in back, and so on. So we could end up with a very ugly- + // looking chopped winding, and this might be undesirable, and would at least lead to + // a possible exhaustion of MAX_POINTS_ON_WINDING. It's better to assume that points + // very very close to the plane are on the plane, using an infinitesimal epsilon amount. + + // Now, the original ChopWindingInPlace() function used a vec_t-based winding_t. + // So this minimum epsilon is quite similar to casting the higher resolution numbers to + // the lower resolution and comparing them in the lower resolution mode. We explicitly + // choose the minimum epsilon as something around the vec_t epsilon of one because we + // want the resolution of vec_accu_t to have a large resolution around the epsilon. + // Some of that leftover resolution even goes away after we scale to points far away. + + // Here is a further discussion regarding the choice of smallestEpsilonAllowed. + // In the 32 float world (we can assume vec_t is that), the "epsilon around 1.0" is + // 0.00000011921. In the 64 bit float world (we can assume vec_accu_t is that), the + // "epsilon around 1.0" is 0.00000000000000022204. (By the way these two epsilons + // are defined as VEC_SMALLEST_EPSILON_AROUND_ONE VEC_ACCU_SMALLEST_EPSILON_AROUND_ONE + // respectively.) If you divide the first by the second, you get approximately + // 536,885,246. Dividing that number by 200,000 (a typical base winding coordinate) + // gives 2684. So in other words, if our smallestEpsilonAllowed was chosen as exactly + // VEC_SMALLEST_EPSILON_AROUND_ONE, you would be guaranteed at least 2000 "ticks" in + // 64-bit land inside of the epsilon for all numbers we're dealing with. + + static const vec_accu_t smallestEpsilonAllowed = ( (vec_accu_t) VEC_SMALLEST_EPSILON_AROUND_ONE ) * 0.5; + if ( crudeEpsilon < smallestEpsilonAllowed ) { + fineEpsilon = smallestEpsilonAllowed; + } + else{fineEpsilon = (vec_accu_t) crudeEpsilon; } + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + VectorCopyRegularToAccu( normal, normalAccu ); + + for ( i = 0; i < in->numpoints; i++ ) + { + dists[i] = DotProductAccu( in->p[i], normalAccu ) - dist; + if ( dists[i] > fineEpsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( dists[i] < -fineEpsilon ) { + sides[i] = SIDE_BACK; + } + else{sides[i] = SIDE_ON; } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // I'm wondering if whatever code that handles duplicate planes is robust enough + // that we never get a case where two nearly equal planes result in 2 NULL windings + // due to the 'if' statement below. TODO: Investigate this. + if ( !counts[SIDE_FRONT] ) { + FreeWindingAccu( in ); + *inout = NULL; + return; + } + if ( !counts[SIDE_BACK] ) { + return; // Winding is unmodified. + } + + // NOTE: The least number of points that a winding can have at this point is 2. + // In that case, one point is SIDE_FRONT and the other is SIDE_BACK. + + maxpts = counts[SIDE_FRONT] + 2; // We dynamically expand if this is too small. + f = AllocWindingAccu( maxpts ); + + for ( i = 0; i < in->numpoints; i++ ) + { + p1 = in->p[i]; + + if ( sides[i] == SIDE_ON || sides[i] == SIDE_FRONT ) { + if ( f->numpoints >= MAX_POINTS_ON_WINDING ) { + Error( "ChopWindingInPlaceAccu: MAX_POINTS_ON_WINDING" ); + } + if ( f->numpoints >= maxpts ) { // This will probably never happen. + Sys_FPrintf( SYS_VRB, "WARNING: estimate on chopped winding size incorrect (no problem)\n" ); + f = CopyWindingAccuIncreaseSizeAndFreeOld( f ); + maxpts++; + } + VectorCopyAccu( p1, f->p[f->numpoints] ); + f->numpoints++; + if ( sides[i] == SIDE_ON ) { + continue; + } + } + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // Generate a split point. + p2 = in->p[( ( i + 1 ) == in->numpoints ) ? 0 : ( i + 1 )]; + + // The divisor's absolute value is greater than the dividend's absolute value. + // w is in the range (0,1). + w = dists[i] / ( dists[i] - dists[i + 1] ); + + for ( j = 0; j < 3; j++ ) + { + // Avoid round-off error when possible. Check axis-aligned normal. + if ( normal[j] == 1 ) { + mid[j] = dist; + } + else if ( normal[j] == -1 ) { + mid[j] = -dist; + } + else{mid[j] = p1[j] + ( w * ( p2[j] - p1[j] ) ); } + } + if ( f->numpoints >= MAX_POINTS_ON_WINDING ) { + Error( "ChopWindingInPlaceAccu: MAX_POINTS_ON_WINDING" ); + } + if ( f->numpoints >= maxpts ) { // This will probably never happen. + Sys_FPrintf( SYS_VRB, "WARNING: estimate on chopped winding size incorrect (no problem)\n" ); + f = CopyWindingAccuIncreaseSizeAndFreeOld( f ); + maxpts++; + } + VectorCopyAccu( mid, f->p[f->numpoints] ); + f->numpoints++; + } + + FreeWindingAccu( in ); + *inout = f; +} + +/* + ============= + ChopWindingInPlace + ============= + */ +void ChopWindingInPlace( winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon ){ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING + 4]; + int sides[MAX_POINTS_ON_WINDING + 4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for ( i = 0 ; i < in->numpoints ; 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 ; i < in->numpoints ; i++ ) + { + p1 = in->p[i]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + + if ( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = in->p[( i + 1 ) % in->numpoints]; + + dot = dists[i] / ( dists[i] - dists[i + 1] ); + for ( j = 0 ; j < 3 ; j++ ) + { // avoid round off error when possible + if ( normal[j] == 1 ) { + mid[j] = dist; + } + else if ( normal[j] == -1 ) { + mid[j] = -dist; + } + else{ + mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + } + + VectorCopy( mid, f->p[f->numpoints] ); + f->numpoints++; + } + + if ( f->numpoints > maxpts ) { + Error( "ClipWinding: points exceeded estimate" ); + } + if ( f->numpoints > MAX_POINTS_ON_WINDING ) { + Error( "ClipWinding: MAX_POINTS_ON_WINDING" ); + } + + FreeWinding( in ); + *inout = f; +} + + +/* + ================= + ChopWinding + + Returns the fragment of in that is on the front side + of the cliping plane. The original is freed. + ================= + */ +winding_t *ChopWinding( winding_t *in, vec3_t normal, vec_t dist ){ + winding_t *f, *b; + + ClipWindingEpsilon( in, normal, dist, ON_EPSILON, &f, &b ); + FreeWinding( in ); + if ( b ) { + FreeWinding( b ); + } + return f; +} + + +/* + ================= + CheckWinding + + ================= + */ +void CheckWinding( winding_t *w ){ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if ( w->numpoints < 3 ) { + Error( "CheckWinding: %i points",w->numpoints ); + } + + area = WindingArea( w ); + if ( area < 1 ) { + Error( "CheckWinding: %f area", area ); + } + + WindingPlane( w, facenormal, &facedist ); + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + p1 = w->p[i]; + + for ( j = 0 ; j < 3 ; j++ ) + if ( p1[j] > MAX_WORLD_COORD || p1[j] < MIN_WORLD_COORD ) { + Error( "CheckFace: MAX_WORLD_COORD exceeded: %f",p1[j] ); + } + + j = i + 1 == w->numpoints ? 0 : i + 1; + + // check the point is on the face plane + d = DotProduct( p1, facenormal ) - facedist; + if ( d < -ON_EPSILON || d > ON_EPSILON ) { + Error( "CheckWinding: point off plane" ); + } + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract( p2, p1, dir ); + + if ( VectorLength( dir ) < ON_EPSILON ) { + Error( "CheckWinding: degenerate edge" ); + } + + CrossProduct( facenormal, dir, edgenormal ); + VectorNormalize( edgenormal, edgenormal ); + edgedist = DotProduct( p1, edgenormal ); + edgedist += ON_EPSILON; + + // all other points must be on front side + for ( j = 0 ; j < w->numpoints ; j++ ) + { + if ( j == i ) { + continue; + } + d = DotProduct( w->p[j], edgenormal ); + if ( d > edgedist ) { + Error( "CheckWinding: non-convex" ); + } + } + } +} + + +/* + ============ + WindingOnPlaneSide + ============ + */ +int WindingOnPlaneSide( winding_t *w, vec3_t normal, vec_t dist ){ + qboolean front, back; + int i; + vec_t d; + + front = qfalse; + back = qfalse; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + d = DotProduct( w->p[i], normal ) - dist; + if ( d < -ON_EPSILON ) { + if ( front ) { + return SIDE_CROSS; + } + back = qtrue; + continue; + } + if ( d > ON_EPSILON ) { + if ( back ) { + return SIDE_CROSS; + } + front = qtrue; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + + +/* + ================= + AddWindingToConvexHull + + Both w and *hull are on the same plane + ================= + */ +#define MAX_HULL_POINTS 128 +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { + int i, j, k; + float *p, *copy; + vec3_t dir; + float d; + int numHullPoints, numNew; + vec3_t hullPoints[MAX_HULL_POINTS]; + vec3_t newHullPoints[MAX_HULL_POINTS]; + vec3_t hullDirs[MAX_HULL_POINTS]; + qboolean hullSide[MAX_HULL_POINTS]; + qboolean outside; + + if ( !*hull ) { + *hull = CopyWinding( w ); + return; + } + + numHullPoints = ( *hull )->numpoints; + memcpy( hullPoints, ( *hull )->p, numHullPoints * sizeof( vec3_t ) ); + + for ( i = 0 ; i < w->numpoints ; i++ ) { + p = w->p[i]; + + // calculate hull side vectors + for ( j = 0 ; j < numHullPoints ; j++ ) { + k = ( j + 1 ) % numHullPoints; + + VectorSubtract( hullPoints[k], hullPoints[j], dir ); + VectorNormalize( dir, dir ); + CrossProduct( normal, dir, hullDirs[j] ); + } + + outside = qfalse; + for ( j = 0 ; j < numHullPoints ; j++ ) { + VectorSubtract( p, hullPoints[j], dir ); + d = DotProduct( dir, hullDirs[j] ); + if ( d >= ON_EPSILON ) { + outside = qtrue; + } + if ( d >= -ON_EPSILON ) { + hullSide[j] = qtrue; + } + else { + hullSide[j] = qfalse; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0 ; j < numHullPoints ; j++ ) { + if ( !hullSide[ j % numHullPoints ] && hullSide[ ( j + 1 ) % numHullPoints ] ) { + break; + } + } + if ( j == numHullPoints ) { + continue; + } + + // insert the point here + VectorCopy( p, newHullPoints[0] ); + numNew = 1; + + // copy over all points that aren't double fronts + j = ( j + 1 ) % numHullPoints; + for ( k = 0 ; k < numHullPoints ; k++ ) { + if ( hullSide[ ( j + k ) % numHullPoints ] && hullSide[ ( j + k + 1 ) % numHullPoints ] ) { + continue; + } + copy = hullPoints[ ( j + k + 1 ) % numHullPoints ]; + VectorCopy( copy, newHullPoints[numNew] ); + numNew++; + } + + numHullPoints = numNew; + memcpy( hullPoints, newHullPoints, numHullPoints * sizeof( vec3_t ) ); + } + + FreeWinding( *hull ); + w = AllocWinding( numHullPoints ); + w->numpoints = numHullPoints; + *hull = w; + memcpy( w->p, hullPoints, numHullPoints * sizeof( vec3_t ) ); +} diff --git a/tools/common/polylib.h b/tools/common/polylib.h new file mode 100644 index 0000000..08bf9aa --- /dev/null +++ b/tools/common/polylib.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +typedef struct +{ + int numpoints; + vec3_t p[1]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 512 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1 +#endif + +winding_t *AllocWinding( int points ); +vec_t WindingArea( winding_t *w ); +void WindingCenter( winding_t *w, vec3_t center ); +void ClipWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ); +void ClipWindingEpsilonStrict( winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back ); +winding_t *ChopWinding( winding_t *in, vec3_t normal, vec_t dist ); +winding_t *CopyWinding( winding_t *w ); +winding_t *ReverseWinding( winding_t *w ); +winding_t *BaseWindingForPlane( vec3_t normal, vec_t dist ); +void CheckWinding( winding_t *w ); +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ); +void RemoveColinearPoints( winding_t *w ); +int WindingOnPlaneSide( winding_t *w, vec3_t normal, vec_t dist ); +void FreeWinding( winding_t *w ); +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs ); + +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void ChopWindingInPlace( winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon ); +// frees the original if clipped + +void pw( winding_t *w ); + + +/////////////////////////////////////////////////////////////////////////////////////// +// Below is double-precision stuff. This was initially needed by the base winding code +// in q3map2 brush processing. +/////////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int numpoints; + vec3_accu_t p[1]; // variable sized +} winding_accu_t; + +winding_accu_t *BaseWindingForPlaneAccu( vec3_t normal, vec_t dist ); +void ChopWindingInPlaceAccu( winding_accu_t **w, vec3_t normal, vec_t dist, vec_t epsilon ); +winding_t *CopyWindingAccuToRegular( winding_accu_t *w ); +void FreeWindingAccu( winding_accu_t *w ); diff --git a/tools/common/polyset.h b/tools/common/polyset.h new file mode 100644 index 0000000..089d0e6 --- /dev/null +++ b/tools/common/polyset.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __POLYSET_H__ +#define __POLYSET_H__ + +#define POLYSET_MAXTRIANGLES 4096 +#define POLYSET_MAXPOLYSETS 64 + +typedef float st_t[2]; +typedef float rgb_t[3]; + +typedef struct { + vec3_t verts[3]; + vec3_t normals[3]; + st_t texcoords[3]; +} triangle_t; + +typedef struct +{ + char name[100]; + char materialname[100]; + triangle_t *triangles; + int numtriangles; +} polyset_t; + +polyset_t *Polyset_LoadSets( const char *file, int *numpolysets, int maxTrisPerSet ); +polyset_t *Polyset_CollapseSets( polyset_t *psets, int numpolysets ); +polyset_t *Polyset_SplitSets( polyset_t *psets, int numpolysets, int *pNumNewPolysets, int maxTris ); +void Polyset_SnapSets( polyset_t *psets, int numpolysets ); +void Polyset_ComputeNormals( polyset_t *psets, int numpolysets ); + +#endif diff --git a/tools/common/qfiles.h b/tools/common/qfiles.h new file mode 100644 index 0000000..3499b6d --- /dev/null +++ b/tools/common/qfiles.h @@ -0,0 +1,490 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES ( 6 * SHADER_MAX_VERTEXES ) + + +// the maximum size of game reletive pathnames +#define MAX_QPATH 64 + +/* + ======================================================================== + + QVM files + + ======================================================================== + */ + +#define VM_MAGIC 0x12721444 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength +} vmHeader_t; + + +/* + ======================================================================== + + PCX files are used for 8 bit images + + ======================================================================== + */ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* + ======================================================================== + + TGA files are used for 24/32 bit images + + ======================================================================== + */ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + + +/* + ======================================================================== + + .MD3 triangle model file format + + ======================================================================== + */ + +#define MD3_IDENT ( ( '3' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 4 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE ( 1.0 / 64 ) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +/* + ============================================================================== + + MD4 file format + + ============================================================================== + */ + +#define MD4_IDENT ( ( '4' << 24 ) + ( 'P' << 16 ) + ( 'D' << 8 ) + 'I' ) +#define MD4_VERSION 1 +#define MD4_MAX_BONES 128 + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list +} md4Weight_t; + +typedef struct { + vec3_t vertex; + vec3_t normal; + float texCoords[2]; + int numWeights; + md4Weight_t weights[1]; // variable sized +} md4Vertex_t; + +typedef struct { + int indexes[3]; +} md4Triangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} md4Surface_t; + +typedef struct { + float matrix[3][4]; +} md4Bone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + char name[16]; + md4Bone_t bones[1]; // [numBones] +} md4Frame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} md4LOD_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // md4Frame_t[numFrames] + + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; + + int ofsEnd; // end of file +} md4Header_t; + + +/* + ============================================================================== + + .BSP file format + + ============================================================================== + */ + + +#define BSP_IDENT ( ( 'P' << 24 ) + ( 'S' << 16 ) + ( 'B' << 8 ) + 'I' ) +// little-endian "IBSP" + +//#define BSP_VERSION 46 +#define Q3_BSP_VERSION 46 +#define WOLF_BSP_VERSION 47 + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x40000 //% 0x20000 /* ydnar */ +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 0x800000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +#define MIN_WORLD_COORD ( -65536 ) +#define MAX_WORLD_COORD ( 65536 ) +#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +//============================================================================= + + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} drawVert_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE, + MST_PATCHFIXED=256, //FTE extension +} mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + + +#endif diff --git a/tools/common/qthreads.h b/tools/common/qthreads.h new file mode 100644 index 0000000..823cb63 --- /dev/null +++ b/tools/common/qthreads.h @@ -0,0 +1,30 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +extern int numthreads; + +void ThreadSetDefault( void ); +int GetThreadWork( void ); +void RunThreadsOnIndividual( int workcnt, qboolean showpacifier, void ( *func )( int ) ); +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ); +void ThreadLock( void ); +void ThreadUnlock( void ); diff --git a/tools/common/scriplib.c b/tools/common/scriplib.c new file mode 100644 index 0000000..62ab9c4 --- /dev/null +++ b/tools/common/scriplib.c @@ -0,0 +1,417 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// scriplib.c + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "scriplib.h" +#include "vfs.h" + +/* + ============================================================================= + + PARSING STUFF + + ============================================================================= + */ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; +} script_t; + +#define MAX_INCLUDES 8 +script_t scriptstack[MAX_INCLUDES]; +script_t *script; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only qtrue if UnGetToken was just called + +/* + ============== + AddScriptToStack + ============== + */ +void AddScriptToStack( const char *filename, int index ){ + int size; + void* buffer; + + script++; + if ( script == &scriptstack[MAX_INCLUDES] ) { + Error( "script file exceeded MAX_INCLUDES" ); + } + strcpy( script->filename, ExpandPath( filename ) ); + + size = vfsLoadFile( script->filename, &buffer, index ); + + if ( size == -1 ) { + Sys_Printf( "Script file %s was not found\n", script->filename ); + script--; + } + else + { + if ( index > 0 ) { + Sys_Printf( "entering %s (%d)\n", script->filename, index + 1 ); + } + else{ + Sys_Printf( "entering %s\n", script->filename ); + } + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + } +} + + +/* + ============== + LoadScriptFile + ============== + */ +void LoadScriptFile( const char *filename, int index ){ + script = scriptstack; + AddScriptToStack( filename, index ); + + endofscript = qfalse; + tokenready = qfalse; +} + + +/* + ============== + ParseFromMemory + ============== + */ +void ParseFromMemory( char *buffer, int size ){ + script = scriptstack; + script++; + if ( script == &scriptstack[MAX_INCLUDES] ) { + Error( "script file exceeded MAX_INCLUDES" ); + } + strcpy( script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = qfalse; + tokenready = qfalse; +} + + +/* + ============== + UnGetToken + + Signals that the current token was not used, and should be reported + for the next GetToken. Note that + + GetToken (qtrue); + UnGetToken (); + GetToken (qfalse); + + could cross a line boundary. + ============== + */ +void UnGetToken( void ){ + tokenready = qtrue; +} + + +qboolean EndOfScript( qboolean crossline ){ + if ( !crossline ) { + Error( "Line %i is incomplete\n",scriptline ); + } + + if ( !strcmp( script->filename, "memory buffer" ) ) { + endofscript = qtrue; + return qfalse; + } + + if ( script->buffer == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Attempt to free already freed script buffer\n" ); + } + else{ + free( script->buffer ); + } + script->buffer = NULL; + if ( script == scriptstack + 1 ) { + endofscript = qtrue; + return qfalse; + } + script--; + scriptline = script->line; + Sys_Printf( "returning to %s\n", script->filename ); + return GetToken( crossline ); +} + +/* + ============== + GetToken + ============== + */ +qboolean GetToken( qboolean crossline ){ + char *token_p; + + + /* ydnar: dummy testing */ + if ( script == NULL || script->buffer == NULL ) { + return qfalse; + } + + if ( tokenready ) { // is a token already waiting? + tokenready = qfalse; + return qtrue; + } + + if ( ( script->script_p >= script->end_p ) || ( script->script_p == NULL ) ) { + return EndOfScript( crossline ); + } + +// +// skip space +// +skipspace: + while ( *script->script_p <= 32 ) + { + if ( script->script_p >= script->end_p ) { + return EndOfScript( crossline ); + } + if ( *script->script_p++ == '\n' ) { + if ( !crossline ) { + Error( "Line %i is incomplete\n",scriptline ); + } + script->line++; + scriptline = script->line; + } + } + + if ( script->script_p >= script->end_p ) { + return EndOfScript( crossline ); + } + + // ; # // comments + if ( *script->script_p == ';' || *script->script_p == '#' + || ( script->script_p[0] == '/' && script->script_p[1] == '/' ) ) { + if ( !crossline ) { + Error( "Line %i is incomplete\n",scriptline ); + } + while ( *script->script_p++ != '\n' ) + if ( script->script_p >= script->end_p ) { + return EndOfScript( crossline ); + } + script->line++; + scriptline = script->line; + goto skipspace; + } + + // /* */ comments + if ( script->script_p[0] == '/' && script->script_p[1] == '*' ) { + if ( !crossline ) { + Error( "Line %i is incomplete\n",scriptline ); + } + script->script_p += 2; + while ( script->script_p[0] != '*' && script->script_p[1] != '/' ) + { + if ( *script->script_p == '\n' ) { + script->line++; + scriptline = script->line; + } + script->script_p++; + if ( script->script_p >= script->end_p ) { + return EndOfScript( crossline ); + } + } + script->script_p += 2; + goto skipspace; + } + +// +// copy token +// + token_p = token; + + if ( *script->script_p == '"' ) { + // quoted token + script->script_p++; + while ( *script->script_p != '"' ) + { + *token_p++ = *script->script_p++; + if ( script->script_p == script->end_p ) { + break; + } + if ( token_p == &token[MAXTOKEN] ) { + Error( "Token too large on line %i\n",scriptline ); + } + } + script->script_p++; + } + else{ // regular token + while ( *script->script_p > 32 && *script->script_p != ';' ) + { + *token_p++ = *script->script_p++; + if ( script->script_p == script->end_p ) { + break; + } + if ( token_p == &token[MAXTOKEN] ) { + Error( "Token too large on line %i\n",scriptline ); + } + } + } + + *token_p = 0; + + if ( !strcmp( token, "$include" ) ) { + GetToken( qfalse ); + AddScriptToStack( token, 0 ); + return GetToken( crossline ); + } + + return qtrue; +} + + +/* + ============== + TokenAvailable + + Returns qtrue if there is another token on the line + ============== + */ +qboolean TokenAvailable( void ) { + int oldLine; + qboolean r; + + /* save */ + oldLine = scriptline; + + /* test */ + r = GetToken( qtrue ); + if ( !r ) { + return qfalse; + } + UnGetToken(); + if ( oldLine == scriptline ) { + return qtrue; + } + + /* restore */ + //% scriptline = oldLine; + //% script->line = oldScriptLine; + + return qfalse; +} + + +//===================================================================== + + +void MatchToken( char *match ) { + GetToken( qtrue ); + + if ( strcmp( token, match ) ) { + Error( "MatchToken( \"%s\" ) failed at line %i in file %s", match, scriptline, script->filename ); + } +} + +void Parse1DMatrix( int x, vec_t *m ) { + int i; + + MatchToken( "(" ); + + for ( i = 0 ; i < x ; i++ ) { + GetToken( qfalse ); + m[i] = atof( token ); + } + + MatchToken( ")" ); +} + +void Parse2DMatrix( int y, int x, vec_t *m ) { + int i; + + MatchToken( "(" ); + + for ( i = 0 ; i < y ; i++ ) { + Parse1DMatrix( x, m + i * x ); + } + + MatchToken( ")" ); +} + +void Parse3DMatrix( int z, int y, int x, vec_t *m ) { + int i; + + MatchToken( "(" ); + + for ( i = 0 ; i < z ; i++ ) { + Parse2DMatrix( y, x, m + i * x * y ); + } + + MatchToken( ")" ); +} + + +void Write1DMatrix( FILE *f, int x, vec_t *m ) { + int i; + + fprintf( f, "( " ); + for ( i = 0 ; i < x ; i++ ) { + if ( m[i] == (int)m[i] ) { + fprintf( f, "%i ", (int)m[i] ); + } + else { + fprintf( f, "%f ", m[i] ); + } + } + fprintf( f, ")" ); +} + +void Write2DMatrix( FILE *f, int y, int x, vec_t *m ) { + int i; + + fprintf( f, "( " ); + for ( i = 0 ; i < y ; i++ ) { + Write1DMatrix( f, x, m + i * x ); + fprintf( f, " " ); + } + fprintf( f, ")\n" ); +} + + +void Write3DMatrix( FILE *f, int z, int y, int x, vec_t *m ) { + int i; + + fprintf( f, "(\n" ); + for ( i = 0 ; i < z ; i++ ) { + Write2DMatrix( f, y, x, m + i * ( x * y ) ); + } + fprintf( f, ")\n" ); +} diff --git a/tools/common/scriplib.h b/tools/common/scriplib.h new file mode 100644 index 0000000..dc9b234 --- /dev/null +++ b/tools/common/scriplib.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// scriplib.h + +#ifndef __CMDLIB__ +#include "../common/cmdlib.h" +#endif +#ifndef __MATHLIB__ +#include "mathlib.h" +#endif + +#define MAXTOKEN 1024 + +extern char token[MAXTOKEN]; +extern char *scriptbuffer,*script_p,*scriptend_p; +extern int grabbed; +extern int scriptline; +extern qboolean endofscript; + + +void LoadScriptFile( const char *filename, int index ); +void ParseFromMemory( char *buffer, int size ); + +qboolean GetToken( qboolean crossline ); +void UnGetToken( void ); +qboolean TokenAvailable( void ); + +void MatchToken( char *match ); + +void Parse1DMatrix( int x, vec_t *m ); +void Parse2DMatrix( int y, int x, vec_t *m ); +void Parse3DMatrix( int z, int y, int x, vec_t *m ); + +void Write1DMatrix( FILE *f, int x, vec_t *m ); +void Write2DMatrix( FILE *f, int y, int x, vec_t *m ); +void Write3DMatrix( FILE *f, int z, int y, int x, vec_t *m ); diff --git a/tools/common/surfaceflags.h b/tools/common/surfaceflags.h new file mode 100644 index 0000000..12b9c25 --- /dev/null +++ b/tools/common/surfaceflags.h @@ -0,0 +1,112 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// Copyright (C) 1999-2000 Id Software, Inc. +// +// This file must be identical in the quake and utils directories + +// contents flags are seperate bits +// a given brush can contribute multiple content bits + +// these definitions also need to be in q_shared.h! + +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_FOG 64 + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 +//bot specific contents types +#define CONTENTS_TELEPORTER 0x40000 +#define CONTENTS_JUMPPAD 0x80000 +#define CONTENTS_CLUSTERPORTAL 0x100000 +#define CONTENTS_DONOTENTER 0x200000 +#define CONTENTS_BOTCLIP 0x400000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_BODY 0x2000000 // should never be on a brush, only in game +#define CONTENTS_CORPSE 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes not used for the bsp +#define CONTENTS_STRUCTURAL 0x10000000 // brushes used for the bsp +#define CONTENTS_TRANSLUCENT 0x20000000 // don't consume surface fragments inside +#define CONTENTS_TRIGGER 0x40000000 +#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava) + +#define SURF_NODAMAGE 0x1 // never give falling damage +#define SURF_SLICK 0x2 // effects game physics +#define SURF_SKY 0x4 // lighting from environment map +#define SURF_LADDER 0x8 +#define SURF_NOIMPACT 0x10 // don't make missile explosions +#define SURF_NOMARKS 0x20 // don't leave missile marks +#define SURF_FLESH 0x40 // make flesh sounds and effects +#define SURF_NODRAW 0x80 // don't generate a drawsurface at all +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes +#define SURF_NOLIGHTMAP 0x400 // surface doesn't need a lightmap +#define SURF_POINTLIGHT 0x800 // generate lighting info at vertexes +#define SURF_METALSTEPS 0x1000 // clanking footsteps +#define SURF_NOSTEPS 0x2000 // no footstep sounds +#define SURF_NONSOLID 0x4000 // don't collide against curves with this set +#define SURF_LIGHTFILTER 0x8000 // act as a light filter during q3map -light +#define SURF_ALPHASHADOW 0x10000 // do per-pixel light shadow casting in q3map +#define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies) +#define SURF_DUST 0x40000 // leave a dust trail when walking on this surface + + + + +/* ydnar flags */ + +#define CONTENTS_OPAQUE 0x02 +#define CONTENTS_LIGHTGRID 0x04 + +#define SURF_VERTEXLIT ( SURF_POINTLIGHT | SURF_NOLIGHTMAP ) + + + +/* wolfenstein flags (collisions with valid q3a flags are noted) */ + +#define CONTENTS_MISSILECLIP 0x80 +#define CONTENTS_ITEM 0x100 +#define CONTENTS_AI_NOSIGHT 0x1000 +#define CONTENTS_CLIPSHOT 0x2000 +#define CONTENTS_DONOTENTER_LARGE 0x400000 /* CONTENTS_BOTCLIP */ + +#define SURF_CERAMIC 0x40 /* SURF_FLESH */ +#define SURF_METAL 0x1000 /* SURF_METALSTEPS */ +#define SURF_WOOD 0x40000 /* SURF_DUST */ +#define SURF_GRASS 0x80000 +#define SURF_GRAVEL 0x100000 +#define SURF_GLASS 0x200000 +#define SURF_SNOW 0x400000 +#define SURF_ROOF 0x800000 +#define SURF_RUBBLE 0x1000000 +#define SURF_CARPET 0x2000000 +#define SURF_MONSTERSLICK 0x4000000 +#define SURF_MONSLICK_W 0x8000000 +#define SURF_MONSLICK_N 0x10000000 +#define SURF_MONSLICK_E 0x20000000 +#define SURF_MONSLICK_S 0x40000000 diff --git a/tools/common/threads.c b/tools/common/threads.c new file mode 100644 index 0000000..fd38ef9 --- /dev/null +++ b/tools/common/threads.c @@ -0,0 +1,646 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "globaldefs.h" +#include +#if !GDEF_OS_WINDOWS +// The below define is necessary to use +// pthreads extensions like pthread_mutexattr_settype +#define _GNU_SOURCE +#include +#endif + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "qthreads.h" + +#define MAX_THREADS 64 + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; + +qboolean threaded; + +/* + ============= + GetThreadWork + + ============= + */ +int GetThreadWork( void ){ + int r; + int f; + + ThreadLock(); + + if ( dispatch == workcount ) { + ThreadUnlock(); + return -1; + } + + f = 40 * dispatch / workcount; + if ( f < oldf ) { + Sys_Printf( "warning: progress went backwards (should never happen)\n" ); + oldf = f; + } + while ( f > oldf ) + { + ++oldf; + if ( pacifier ) { + if ( oldf % 4 == 0 ) { + Sys_Printf( "%i", f / 4 ); + } + else{ + Sys_Printf( "." ); + } + fflush( stdout ); /* ydnar */ + } + } + + r = dispatch; + dispatch++; + ThreadUnlock(); + + return r; +} + + +void ( *workfunction )( int ); + +void ThreadWorkerFunction( int threadnum ){ + int work; + + while ( 1 ) + { + work = GetThreadWork(); + if ( work == -1 ) { + break; + } +//Sys_Printf ("thread %i, work %i\n", threadnum, work); + workfunction( work ); + } +} + +void RunThreadsOnIndividual( int workcnt, qboolean showpacifier, void ( *func )( int ) ){ + if ( numthreads == -1 ) { + ThreadSetDefault(); + } + workfunction = func; + RunThreadsOn( workcnt, showpacifier, ThreadWorkerFunction ); +} + + +/* + =================================================================== + + WIN32 + + =================================================================== + */ +#if GDEF_OS_WINDOWS + +#define USED + +#include + +int numthreads = -1; +CRITICAL_SECTION crit; +static int enter; + +void ThreadSetDefault( void ){ + SYSTEM_INFO info; + + if ( numthreads == -1 ) { // not set manually + GetSystemInfo( &info ); + numthreads = info.dwNumberOfProcessors; + if ( numthreads < 1 || numthreads > 32 ) { + numthreads = 1; + } + } + + Sys_Printf( "%i threads\n", numthreads ); +} + + +void ThreadLock( void ){ + if ( !threaded ) { + return; + } + EnterCriticalSection( &crit ); + if ( enter ) { + Error( "Recursive ThreadLock\n" ); + } + enter = 1; +} + +void ThreadUnlock( void ){ + if ( !threaded ) { + return; + } + if ( !enter ) { + Error( "ThreadUnlock without lock\n" ); + } + enter = 0; + LeaveCriticalSection( &crit ); +} + +/* + ============= + RunThreadsOn + ============= + */ +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ){ + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + start = I_FloatTime(); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = qtrue; + + // + // run threads in parallel + // + InitializeCriticalSection( &crit ); + + if ( numthreads == 1 ) { // use same thread + func( 0 ); + } + else + { + for ( i = 0 ; i < numthreads ; i++ ) + { + threadhandle[i] = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + //0, // DWORD cbStack, + + /* ydnar: cranking stack size to eliminate radiosity crash with 1MB stack on win32 */ + ( 4096 * 1024 ), + + (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, + (LPVOID)i, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &threadid[i] ); + } + + for ( i = 0 ; i < numthreads ; i++ ) + WaitForSingleObject( threadhandle[i], INFINITE ); + } + DeleteCriticalSection( &crit ); + + threaded = qfalse; + end = I_FloatTime(); + if ( pacifier ) { + Sys_Printf( " (%i)\n", end - start ); + } +} + + +#endif + +/* + =================================================================== + + OSF1 + + =================================================================== + */ + +#ifdef __osf__ +#define USED + +int numthreads = 4; + +void ThreadSetDefault( void ){ + if ( numthreads == -1 ) { // not set manually + numthreads = 4; + } +} + + +#include + +pthread_mutex_t *my_mutex; + +void ThreadLock( void ){ + if ( my_mutex ) { + pthread_mutex_lock( my_mutex ); + } +} + +void ThreadUnlock( void ){ + if ( my_mutex ) { + pthread_mutex_unlock( my_mutex ); + } +} + + +/* + ============= + RunThreadsOn + ============= + */ +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ){ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + start = I_FloatTime(); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = qtrue; + + if ( pacifier ) { + setbuf( stdout, NULL ); + } + + if ( !my_mutex ) { + my_mutex = safe_malloc( sizeof( *my_mutex ) ); + if ( pthread_mutexattr_create( &mattrib ) == -1 ) { + Error( "pthread_mutex_attr_create failed" ); + } + if ( pthread_mutexattr_setkind_np( &mattrib, MUTEX_FAST_NP ) == -1 ) { + Error( "pthread_mutexattr_setkind_np failed" ); + } + if ( pthread_mutex_init( my_mutex, mattrib ) == -1 ) { + Error( "pthread_mutex_init failed" ); + } + } + + if ( pthread_attr_create( &attrib ) == -1 ) { + Error( "pthread_attr_create failed" ); + } + if ( pthread_attr_setstacksize( &attrib, 0x100000 ) == -1 ) { + Error( "pthread_attr_setstacksize failed" ); + } + + for ( i = 0 ; i < numthreads ; i++ ) + { + if ( pthread_create( &work_threads[i], attrib + , (pthread_startroutine_t)func, (pthread_addr_t)i ) == -1 ) { + Error( "pthread_create failed" ); + } + } + + for ( i = 0 ; i < numthreads ; i++ ) + { + if ( pthread_join( work_threads[i], &status ) == -1 ) { + Error( "pthread_join failed" ); + } + } + + threaded = qfalse; + + end = I_FloatTime(); + if ( pacifier ) { + Sys_Printf( " (%i)\n", end - start ); + } +} + + +#endif + +/* + =================================================================== + + IRIX + + =================================================================== + */ + +#ifdef _MIPS_ISA +#define USED + +#include +#include +#include +#include + + +int numthreads = -1; +abilock_t lck; + +void ThreadSetDefault( void ){ + if ( numthreads == -1 ) { + numthreads = prctl( PR_MAXPPROCS ); + } + Sys_Printf( "%i threads\n", numthreads ); + usconfig( CONF_INITUSERS, numthreads ); +} + + +void ThreadLock( void ){ + spin_lock( &lck ); +} + +void ThreadUnlock( void ){ + release_lock( &lck ); +} + + +/* + ============= + RunThreadsOn + ============= + */ +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ){ + int i; + int pid[MAX_THREADS]; + int start, end; + + start = I_FloatTime(); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = qtrue; + + if ( pacifier ) { + setbuf( stdout, NULL ); + } + + init_lock( &lck ); + + for ( i = 0 ; i < numthreads - 1 ; i++ ) + { + pid[i] = sprocsp( ( void ( * )( void *, size_t ) )func, PR_SALL, (void *)i + , NULL, 0x200000 ); // 2 meg stacks + if ( pid[i] == -1 ) { + perror( "sproc" ); + Error( "sproc failed" ); + } + } + + func( i ); + + for ( i = 0 ; i < numthreads - 1 ; i++ ) + wait( NULL ); + + threaded = qfalse; + + end = I_FloatTime(); + if ( pacifier ) { + Sys_Printf( " (%i)\n", end - start ); + } +} + + +#endif + + +/* + ======================================================================= + + Linux pthreads + + ======================================================================= + */ + +#if GDEF_OS_LINUX || ( GDEF_OS_MACOS && !MAC_STATIC_HACK ) +#define USED + +#include + +int numthreads = -1; + +void ThreadSetDefault( void ){ + if ( numthreads == -1 ) { // not set manually +#ifdef _SC_NPROCESSORS_ONLN + long cpus = sysconf( _SC_NPROCESSORS_ONLN ); + if ( cpus > 0 ) { + numthreads = cpus; + } + else +#endif + /* can't detect, so default to four threads */ + numthreads = 4; + } + + if ( numthreads > 1 ) { + Sys_Printf( "threads: %d\n", numthreads ); + } +} + +#include + +typedef struct pt_mutex_s +{ + pthread_t *owner; + pthread_mutex_t a_mutex; + pthread_cond_t cond; + unsigned int lock; +} pt_mutex_t; + +pt_mutex_t global_lock; + +void ThreadLock( void ){ + pt_mutex_t *pt_mutex = &global_lock; + + if ( !threaded ) { + return; + } + + pthread_mutex_lock( &pt_mutex->a_mutex ); + if ( pthread_equal( pthread_self(), (pthread_t)&pt_mutex->owner ) ) { + pt_mutex->lock++; + } + else + { + if ( ( !pt_mutex->owner ) && ( pt_mutex->lock == 0 ) ) { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + } + else + { + while ( 1 ) + { + pthread_cond_wait( &pt_mutex->cond, &pt_mutex->a_mutex ); + if ( ( !pt_mutex->owner ) && ( pt_mutex->lock == 0 ) ) { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + break; + } + } + } + } + pthread_mutex_unlock( &pt_mutex->a_mutex ); +} + +void ThreadUnlock( void ){ + pt_mutex_t *pt_mutex = &global_lock; + + if ( !threaded ) { + return; + } + + pthread_mutex_lock( &pt_mutex->a_mutex ); + pt_mutex->lock--; + + if ( pt_mutex->lock == 0 ) { + pt_mutex->owner = NULL; + pthread_cond_signal( &pt_mutex->cond ); + } + + pthread_mutex_unlock( &pt_mutex->a_mutex ); +} + +void recursive_mutex_init( pthread_mutexattr_t attribs ){ + pt_mutex_t *pt_mutex = &global_lock; + + pt_mutex->owner = NULL; + if ( pthread_mutex_init( &pt_mutex->a_mutex, &attribs ) != 0 ) { + Error( "pthread_mutex_init failed\n" ); + } + if ( pthread_cond_init( &pt_mutex->cond, NULL ) != 0 ) { + Error( "pthread_cond_init failed\n" ); + } + + pt_mutex->lock = 0; +} + +/* + ============= + RunThreadsOn + ============= + */ +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ){ + pthread_mutexattr_t mattrib; + pthread_attr_t attr; + pthread_t work_threads[MAX_THREADS]; + size_t stacksize; + + int start, end; + int i = 0; + + start = I_FloatTime(); + pacifier = showpacifier; + + dispatch = 0; + oldf = -1; + workcount = workcnt; + + pthread_attr_init( &attr ); + if ( pthread_attr_setstacksize( &attr, 8388608 ) != 0 ) { + stacksize = 0; + pthread_attr_getstacksize( &attr, &stacksize ); + Sys_Printf( "Could not set a per-thread stack size of 8 MB, using only %.2f MB\n", stacksize / 1048576.0 ); + } + + if ( numthreads == 1 ) { + func( 0 ); + } + else + { + threaded = qtrue; + + if ( pacifier ) { + setbuf( stdout, NULL ); + } + + if ( pthread_mutexattr_init( &mattrib ) != 0 ) { + Error( "pthread_mutexattr_init failed" ); + } + if ( pthread_mutexattr_settype( &mattrib, PTHREAD_MUTEX_ERRORCHECK ) != 0 ) { + Error( "pthread_mutexattr_settype failed" ); + } + recursive_mutex_init( mattrib ); + + for ( i = 0 ; i < numthreads ; i++ ) + { + /* Default pthread attributes: joinable & non-realtime scheduling */ + if ( pthread_create(&work_threads[i], &attr, (void *(*)(void *)) func, (void*)(uintptr_t)i ) != 0 ) { + Error( "pthread_create failed" ); + } + } + for ( i = 0 ; i < numthreads ; i++ ) + { + if ( pthread_join( work_threads[i], NULL ) != 0 ) { + Error( "pthread_join failed" ); + } + } + pthread_mutexattr_destroy( &mattrib ); + threaded = qfalse; + } + + end = I_FloatTime(); + if ( pacifier ) { + Sys_Printf( " (%i)\n", end - start ); + } +} +#endif // ifdef __linux__ + + +/* + ======================================================================= + + SINGLE THREAD + + ======================================================================= + */ + +#ifndef USED + +int numthreads = 1; + +void ThreadSetDefault( void ){ + numthreads = 1; +} + +void ThreadLock( void ){ +} + +void ThreadUnlock( void ){ +} + +/* + ============= + RunThreadsOn + ============= + */ +void RunThreadsOn( int workcnt, qboolean showpacifier, void ( *func )( int ) ){ + int start, end; + + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + start = I_FloatTime(); + func( 0 ); + + end = I_FloatTime(); + if ( pacifier ) { + Sys_Printf( " (%i)\n", end - start ); + } +} + +#endif diff --git a/tools/common/trilib.c b/tools/common/trilib.c new file mode 100644 index 0000000..5f40341 --- /dev/null +++ b/tools/common/trilib.c @@ -0,0 +1,237 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// +// trilib.c: library for loading triangles from an Alias triangle file +// + +#include "globaldefs.h" +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "polyset.h" +#include "trilib.h" + +// on disk representation of a face + + +#define FLOAT_START 99999.0 +#define FLOAT_END -FLOAT_START +#define MAGIC 123322 + +//#define NOISY 1 + +#if GDEF_OS_LINUX || GDEF_OS_MACOS +#define strlwr strlower +#endif + +typedef struct { + float v[3]; +} vector; + +typedef struct +{ + vector n; /* normal */ + vector p; /* point */ + vector c; /* color */ + float u; /* u */ + float v; /* v */ +} aliaspoint_t; + +typedef struct { + aliaspoint_t pt[3]; +} tf_triangle; + + +static void ByteSwapTri( tf_triangle *tri ){ + unsigned int i; + + for ( i = 0 ; i < sizeof( tf_triangle ) / 4 ; i++ ) + { + ( (int *)tri )[i] = BigLong( ( (int *)tri )[i] ); + } +} + +static void ReadPolysetGeometry( triangle_t *tripool, FILE *input, int count, triangle_t *ptri ){ + tf_triangle tri; + int i; + + for ( i = 0; i < count; ++i ) { + int j; + + fread( &tri, sizeof( tf_triangle ), 1, input ); + ByteSwapTri( &tri ); + for ( j = 0 ; j < 3 ; j++ ) + { + int k; + + for ( k = 0 ; k < 3 ; k++ ) + { + ptri->verts[j][k] = tri.pt[j].p.v[k]; + ptri->normals[j][k] = tri.pt[j].n.v[k]; +// ptri->colors[j][k] = tri.pt[j].c.v[k]; + } + + ptri->texcoords[j][0] = tri.pt[j].u; + ptri->texcoords[j][1] = tri.pt[j].v; + } + + ptri++; + if ( ( ptri - tripool ) >= POLYSET_MAXTRIANGLES ) { + Error( "Error: too many triangles; increase POLYSET_MAXTRIANGLES\n" ); + } + } +} + +void TRI_LoadPolysets( const char *filename, polyset_t **ppPSET, int *numpsets ){ + FILE *input; + float start; + char name[256], tex[256]; + int i, count, magic, pset = 0; + triangle_t *ptri; + polyset_t *pPSET; + int iLevel; + int exitpattern; + float t; + + t = -FLOAT_START; + *( (unsigned char *)&exitpattern + 0 ) = *( (unsigned char *)&t + 3 ); + *( (unsigned char *)&exitpattern + 1 ) = *( (unsigned char *)&t + 2 ); + *( (unsigned char *)&exitpattern + 2 ) = *( (unsigned char *)&t + 1 ); + *( (unsigned char *)&exitpattern + 3 ) = *( (unsigned char *)&t + 0 ); + + if ( ( input = fopen( filename, "rb" ) ) == 0 ) { + Error( "reader: could not open file '%s'", filename ); + } + + iLevel = 0; + + fread( &magic, sizeof( int ), 1, input ); + if ( BigLong( magic ) != MAGIC ) { + Error( "%s is not a Alias object separated triangle file, magic number is wrong.", filename ); + } + + pPSET = calloc( 1, POLYSET_MAXPOLYSETS * sizeof( polyset_t ) ); + ptri = calloc( 1, POLYSET_MAXTRIANGLES * sizeof( triangle_t ) ); + + *ppPSET = pPSET; + + while ( feof( input ) == 0 ) { + if ( fread( &start, sizeof( float ), 1, input ) < 1 ) { + break; + } + *(int *)&start = BigLong( *(int *)&start ); + if ( *(int *)&start != exitpattern ) { + if ( start == FLOAT_START ) { + /* Start of an object or group of objects. */ + i = -1; + do { + /* There are probably better ways to read a string from */ + /* a file, but this does allow you to do error checking */ + /* (which I'm not doing) on a per character basis. */ + ++i; + fread( &( name[i] ), sizeof( char ), 1, input ); + } while ( name[i] != '\0' ); + + if ( i != 0 ) { + strncpy( pPSET[pset].name, name, sizeof( pPSET[pset].name ) - 1 ); + } + else{ + strcpy( pPSET[pset].name, "(unnamed)" ); + } + strlwr( pPSET[pset].name ); + +// indent(); +// fprintf(stdout,"OBJECT START: %s\n",name); + fread( &count, sizeof( int ), 1, input ); + count = BigLong( count ); + ++iLevel; + if ( count != 0 ) { +// indent(); +// fprintf(stdout,"NUMBER OF TRIANGLES: %d\n",count); + + i = -1; + do { + ++i; + fread( &( tex[i] ), sizeof( char ), 1, input ); + } while ( tex[i] != '\0' ); + +/* + if ( i != 0 ) + strncpy( pPSET[pset].texname, tex, sizeof( pPSET[pset].texname ) - 1 ); + else + strcpy( pPSET[pset].texname, "(unnamed)" ); + strlwr( pPSET[pset].texname ); + */ + +// indent(); +// fprintf(stdout," Object texture name: '%s'\n",tex); + } + + /* Else (count == 0) this is the start of a group, and */ + /* no texture name is present. */ + } + else if ( start == FLOAT_END ) { + /* End of an object or group. Yes, the name should be */ + /* obvious from context, but it is in here just to be */ + /* safe and to provide a little extra information for */ + /* those who do not wish to write a recursive reader. */ + /* Mea culpa. */ + --iLevel; + i = -1; + do { + ++i; + fread( &( name[i] ), sizeof( char ), 1, input ); + } while ( name[i] != '\0' ); + + if ( i != 0 ) { + strncpy( pPSET[pset].name, name, sizeof( pPSET[pset].name ) - 1 ); + } + else{ + strcpy( pPSET[pset].name, "(unnamed)" ); + } + + strlwr( pPSET[pset].name ); + +// indent(); +// fprintf(stdout,"OBJECT END: %s\n",name); + continue; + } + } + +// +// read the triangles +// + if ( count > 0 ) { + pPSET[pset].triangles = ptri; + ReadPolysetGeometry( pPSET[0].triangles, input, count, ptri ); + ptri += count; + pPSET[pset].numtriangles = count; + if ( ++pset >= POLYSET_MAXPOLYSETS ) { + Error( "Error: too many polysets; increase POLYSET_MAXPOLYSETS\n" ); + } + } + } + + *numpsets = pset; + + fclose( input ); +} diff --git a/tools/common/trilib.h b/tools/common/trilib.h new file mode 100644 index 0000000..47dc14b --- /dev/null +++ b/tools/common/trilib.h @@ -0,0 +1,25 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// +// trilib.h: header file for loading triangles from an Alias triangle file +// +void TRI_LoadPolysets( const char *filename, polyset_t **ppPSET, int *numpsets ); diff --git a/tools/common/vfs.c b/tools/common/vfs.c new file mode 100644 index 0000000..16a7828 --- /dev/null +++ b/tools/common/vfs.c @@ -0,0 +1,425 @@ +/* + 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 +#include +#include + +#include "cmdlib.h" +#include "filematch.h" +#include "mathlib.h" +#include "inout.h" +#include "vfs.h" +#include +#include + +typedef struct +{ + char* name; + unzFile zipfile; + unz_file_pos zippos; + guint32 size; +} VFS_PAKFILE; + +// ============================================================================= +// Global variables + +static GSList* g_unzFiles; +static GSList* g_pakFiles; +static char g_strDirs[VFS_MAXDIRS][PATH_MAX + 1]; +static int g_numDirs; +char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1]; +int g_numForbiddenDirs = 0; +static gboolean g_bUsePak = TRUE; + +// ============================================================================= +// Static functions + +static void vfsAddSlash( char *str ){ + int n = strlen( str ); + if ( n > 0 ) { + if ( str[n - 1] != '\\' && str[n - 1] != '/' ) { + strcat( str, "/" ); + } + } +} + +static void vfsFixDOSName( char *src ){ + if ( src == NULL ) { + return; + } + + while ( *src ) + { + if ( *src == '\\' ) { + *src = '/'; + } + src++; + } +} + +//!\todo Define globally or use heap-allocated string. +#define NAME_MAX 255 + +static void vfsInitPakFile( const char *filename ){ + unz_global_info gi; + unzFile uf; + guint32 i; + int err; + + uf = unzOpen( filename ); + if ( uf == NULL ) { + return; + } + + g_unzFiles = g_slist_append( g_unzFiles, uf ); + + err = unzGetGlobalInfo( uf,&gi ); + if ( err != UNZ_OK ) { + return; + } + unzGoToFirstFile( uf ); + + for ( i = 0; i < gi.number_entry; i++ ) + { + char filename_inzip[NAME_MAX]; + char *filename_lower; + unz_file_info file_info; + VFS_PAKFILE* file; + + err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 ); + if ( err != UNZ_OK ) { + break; + } + unz_file_pos pos; + err = unzGetFilePos( uf, &pos ); + if ( err != UNZ_OK ) { + break; + } + + file = (VFS_PAKFILE*)safe_malloc( sizeof( VFS_PAKFILE ) ); + g_pakFiles = g_slist_append( g_pakFiles, file ); + + vfsFixDOSName( filename_inzip ); + //-1 null terminated string + filename_lower = g_ascii_strdown( filename_inzip, -1 ); + + file->name = strdup( filename_lower ); + file->size = file_info.uncompressed_size; + file->zipfile = uf; + file->zippos = pos; + + if ( ( i + 1 ) < gi.number_entry ) { + err = unzGoToNextFile( uf ); + if ( err != UNZ_OK ) { + break; + } + } + g_free( filename_lower ); + } +} + +// ============================================================================= +// Global functions + +// reads all pak files from a dir +void vfsInitDirectory( const char *path ){ + char filename[PATH_MAX]; + char *dirlist; + GDir *dir; + int j; + + for ( j = 0; j < g_numForbiddenDirs; ++j ) + { + char* dbuf = g_strdup( path ); + if ( *dbuf && dbuf[strlen( dbuf ) - 1] == '/' ) { + dbuf[strlen( dbuf ) - 1] = 0; + } + const char *p = strrchr( dbuf, '/' ); + p = ( p ? ( p + 1 ) : dbuf ); + if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) { + g_free( dbuf ); + break; + } + g_free( dbuf ); + } + if ( j < g_numForbiddenDirs ) { + return; + } + + if ( g_numDirs == VFS_MAXDIRS ) { + return; + } + + Sys_Printf( "VFS Init: %s\n", path ); + + strncpy( g_strDirs[g_numDirs], path, PATH_MAX ); + g_strDirs[g_numDirs][PATH_MAX] = 0; + vfsFixDOSName( g_strDirs[g_numDirs] ); + vfsAddSlash( g_strDirs[g_numDirs] ); + g_numDirs++; + + if ( g_bUsePak ) { + dir = g_dir_open( path, 0, NULL ); + + if ( dir != NULL ) { + while ( 1 ) + { + const char* name = g_dir_read_name( dir ); + if ( name == NULL ) { + break; + } + + for ( j = 0; j < g_numForbiddenDirs; ++j ) + { + const char *p = strrchr( name, '/' ); + p = ( p ? ( p + 1 ) : name ); + if ( matchpattern( p, g_strForbiddenDirs[j], TRUE ) ) { + break; + } + } + if ( j < g_numForbiddenDirs ) { + continue; + } + + dirlist = g_strdup( name ); + + { + char *ext = strrchr( dirlist, '.' ); + + if ( ext != NULL && ( !Q_stricmp( ext, ".pk3dir" ) || !Q_stricmp( ext, ".dpkdir" ) ) ) { + if ( g_numDirs == VFS_MAXDIRS ) { + continue; + } + snprintf( g_strDirs[g_numDirs], PATH_MAX, "%s/%s", path, name ); + g_strDirs[g_numDirs][PATH_MAX-1] = '\0'; + vfsFixDOSName( g_strDirs[g_numDirs] ); + vfsAddSlash( g_strDirs[g_numDirs] ); + ++g_numDirs; + } + + if ( ext == NULL || ( Q_stricmp( ext, ".pk3" ) != 0 && Q_stricmp( ext, ".dpk" ) != 0 ) ) { + continue; + } + } + + sprintf( filename, "%s/%s", path, dirlist ); + vfsInitPakFile( filename ); + + g_free( dirlist ); + } + g_dir_close( dir ); + } + } +} + +// frees all memory that we allocated +void vfsShutdown(){ + while ( g_unzFiles ) + { + unzClose( (unzFile)g_unzFiles->data ); + g_unzFiles = g_slist_remove( g_unzFiles, g_unzFiles->data ); + } + + while ( g_pakFiles ) + { + VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data; + free( file->name ); + free( file ); + g_pakFiles = g_slist_remove( g_pakFiles, file ); + } +} + +// return the number of files that match +int vfsGetFileCount( const char *filename ){ + int i, count = 0; + char fixed[NAME_MAX], tmp[NAME_MAX]; + char *lower; + GSList *lst; + + strcpy( fixed, filename ); + vfsFixDOSName( fixed ); + lower = g_ascii_strdown( fixed, -1 ); + + for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) ) + { + VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data; + + if ( strcmp( file->name, lower ) == 0 ) { + count++; + } + } + + for ( i = 0; i < g_numDirs; i++ ) + { + strcpy( tmp, g_strDirs[i] ); + strcat( tmp, lower ); + if ( access( tmp, R_OK ) == 0 ) { + count++; + } + } + g_free( lower ); + return count; +} + +// NOTE: when loading a file, you have to allocate one extra byte and set it to \0 +int vfsLoadFile( const char *filename, void **bufferptr, int index ){ + int i, count = 0; + char tmp[NAME_MAX], fixed[NAME_MAX]; + char *lower; + GSList *lst; + + // filename is a full path + if ( index == -1 ) { + long len; + FILE *f; + + f = fopen( filename, "rb" ); + if ( f == NULL ) { + return -1; + } + + fseek( f, 0, SEEK_END ); + len = ftell( f ); + rewind( f ); + + *bufferptr = safe_malloc( len + 1 ); + if ( *bufferptr == NULL ) { + fclose( f ); + return -1; + } + + if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) { + fclose( f ); + return -1; + } + fclose( f ); + + // we need to end the buffer with a 0 + ( (char*) ( *bufferptr ) )[len] = 0; + + return len; + } + + *bufferptr = NULL; + strcpy( fixed, filename ); + vfsFixDOSName( fixed ); + lower = g_ascii_strdown( fixed, -1 ); + + for ( i = 0; i < g_numDirs; i++ ) + { + strcpy( tmp, g_strDirs[i] ); + strcat( tmp, filename ); + if ( access( tmp, R_OK ) == 0 ) { + if ( count == index ) { + long len; + FILE *f; + + f = fopen( tmp, "rb" ); + if ( f == NULL ) { + return -1; + } + + fseek( f, 0, SEEK_END ); + len = ftell( f ); + rewind( f ); + + *bufferptr = safe_malloc( len + 1 ); + if ( *bufferptr == NULL ) { + fclose( f ); + return -1; + } + + if ( fread( *bufferptr, 1, len, f ) != (size_t) len ) { + fclose( f ); + return -1; + } + fclose( f ); + + // we need to end the buffer with a 0 + ( (char*) ( *bufferptr ) )[len] = 0; + + return len; + } + + count++; + } + } + + for ( lst = g_pakFiles; lst != NULL; lst = g_slist_next( lst ) ) + { + VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data; + + if ( strcmp( file->name, lower ) != 0 ) { + continue; + } + + if ( count == index ) { + + if ( unzGoToFilePos( file->zipfile, &file->zippos ) != UNZ_OK ) { + return -1; + } + if ( unzOpenCurrentFile( file->zipfile ) != UNZ_OK ) { + return -1; + } + + *bufferptr = safe_malloc( file->size + 1 ); + // we need to end the buffer with a 0 + ( (char*) ( *bufferptr ) )[file->size] = 0; + + i = unzReadCurrentFile( file->zipfile, *bufferptr, file->size ); + unzCloseCurrentFile( file->zipfile ); + if ( i < 0 ) { + return -1; + } + else{ + g_free( lower ); + return file->size; + } + } + + count++; + } + g_free( lower ); + return -1; +} diff --git a/tools/common/vfs.h b/tools/common/vfs.h new file mode 100644 index 0000000..519c7fe --- /dev/null +++ b/tools/common/vfs.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2001, Loki software, inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of Loki software nor the names of its contributors may be used + to endorse or promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _VFS_H_ +#define _VFS_H_ + +#include "globaldefs.h" + +// to get PATH_MAX +#include +#if GDEF_OS_LINUX || GDEF_OS_MACOS || GDEF_OS_BSD +#include +#include +#else +#include +#include +#define S_ISDIR( mode ) ( mode & _S_IFDIR ) +#define PATH_MAX 260 +#endif + +#define VFS_MAXDIRS 64 + +void vfsInitDirectory( const char *path ); +void vfsShutdown(); +int vfsGetFileCount( const char *filename ); +int vfsLoadFile( const char *filename, void **buffer, int index ); + +extern char g_strForbiddenDirs[VFS_MAXDIRS][PATH_MAX + 1]; +extern int g_numForbiddenDirs; + +#endif // _VFS_H_ diff --git a/tools/vmap/brush.c b/tools/vmap/brush.c new file mode 100644 index 0000000..12088a1 --- /dev/null +++ b/tools/vmap/brush.c @@ -0,0 +1,1127 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BRUSH_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* ------------------------------------------------------------------------------- + + functions + + ------------------------------------------------------------------------------- */ + +/* + AllocSideRef() - ydnar + allocates and assigns a brush side reference + */ + +sideRef_t *AllocSideRef( side_t *side, sideRef_t *next ){ + sideRef_t *sideRef; + + + /* dummy check */ + if ( side == NULL ) { + return next; + } + + /* allocate and return */ + sideRef = safe_malloc( sizeof( *sideRef ) ); + sideRef->side = side; + sideRef->next = next; + return sideRef; +} + + + +/* + CountBrushList() + counts the number of brushes in a brush linked list + */ + +int CountBrushList( brush_t *brushes ){ + int c = 0; + + + /* count brushes */ + for ( ; brushes != NULL; brushes = brushes->next ) + c++; + return c; +} + + + +/* + AllocBrush() + allocates a new brush + */ + +brush_t *AllocBrush( int numSides ){ + brush_t *bb; + size_t c; + + /* allocate and clear */ + if ( numSides <= 0 ) { + Error( "AllocBrush called with numsides = %d", numSides ); + } + c = (size_t)&( ( (brush_t*) 0 )->sides[ numSides ] ); + bb = safe_malloc( c ); + memset( bb, 0, c ); + if ( numthreads == 1 ) { + numActiveBrushes++; + } + + /* return it */ + return bb; +} + + + +/* + FreeBrush() + frees a single brush and all sides/windings + */ + +void FreeBrush( brush_t *b ){ + int i; + + + /* error check */ + if ( *( (unsigned int*) b ) == 0xFEFEFEFE ) { + Sys_FPrintf( SYS_VRB, "WARNING: Attempt to free an already freed brush!\n" ); + return; + } + + /* free brush sides */ + for ( i = 0; i < b->numsides; i++ ) + if ( b->sides[i].winding != NULL ) { + FreeWinding( b->sides[ i ].winding ); + } + + /* ydnar: overwrite it */ + memset( b, 0xFE, (size_t)&( ( (brush_t*) 0 )->sides[ b->numsides ] ) ); + *( (unsigned int*) b ) = 0xFEFEFEFE; + + /* free it */ + free( b ); + if ( numthreads == 1 ) { + numActiveBrushes--; + } +} + + + +/* + FreeBrushList() + frees a linked list of brushes + */ + +void FreeBrushList( brush_t *brushes ){ + brush_t *next; + + + /* walk brush list */ + for ( ; brushes != NULL; brushes = next ) + { + next = brushes->next; + FreeBrush( brushes ); + } +} + + + +/* + CopyBrush() + duplicates the brush, sides, and windings + */ + +brush_t *CopyBrush( brush_t *brush ){ + brush_t *newBrush; + size_t size; + int i; + + + /* copy brush */ + size = (size_t)&( ( (brush_t*) 0 )->sides[ brush->numsides ] ); + newBrush = AllocBrush( brush->numsides ); + memcpy( newBrush, brush, size ); + + /* ydnar: nuke linked list */ + newBrush->next = NULL; + + /* copy sides */ + for ( i = 0; i < brush->numsides; i++ ) + { + if ( brush->sides[ i ].winding != NULL ) { + newBrush->sides[ i ].winding = CopyWinding( brush->sides[ i ].winding ); + } + } + + /* return it */ + return newBrush; +} + + + + +/* + BoundBrush() + sets the mins/maxs based on the windings + returns false if the brush doesn't enclose a valid volume + */ + +qboolean BoundBrush( brush_t *brush ){ + int i, j; + winding_t *w; + + + ClearBounds( brush->mins, brush->maxs ); + for ( i = 0; i < brush->numsides; i++ ) + { + w = brush->sides[ i ].winding; + if ( w == NULL ) { + continue; + } + for ( j = 0; j < w->numpoints; j++ ) + AddPointToBounds( w->p[ j ], brush->mins, brush->maxs ); + } + + for ( i = 0; i < 3; i++ ) + { + if ( brush->mins[ i ] < MIN_WORLD_COORD || brush->maxs[ i ] > MAX_WORLD_COORD || brush->mins[i] >= brush->maxs[ i ] ) { + return qfalse; + } + } + + return qtrue; +} + + + + +/* + SnapWeldVector() - ydnar + welds two vec3_t's into a third, taking into account nearest-to-integer + instead of averaging + */ + +#define SNAP_EPSILON 0.01 + +void SnapWeldVector( vec3_t a, vec3_t b, vec3_t out ){ + int i; + vec_t ai, bi, outi; + + + /* dummy check */ + if ( a == NULL || b == NULL || out == NULL ) { + return; + } + + /* do each element */ + for ( i = 0; i < 3; i++ ) + { + /* round to integer */ + ai = Q_rint( a[ i ] ); + bi = Q_rint( b[ i ] ); + + /* prefer exact integer */ + if ( ai == a[ i ] ) { + out[ i ] = a[ i ]; + } + else if ( bi == b[ i ] ) { + out[ i ] = b[ i ]; + } + + /* use nearest */ + else if ( fabs( ai - a[ i ] ) < fabs( bi - b[ i ] ) ) { + out[ i ] = a[ i ]; + } + else{ + out[ i ] = b[ i ]; + } + + /* snap */ + outi = Q_rint( out[ i ] ); + if ( fabs( outi - out[ i ] ) <= SNAP_EPSILON ) { + out[ i ] = outi; + } + } +} + + + +/* + ================== + SnapWeldVectorAccu + + Welds two vectors into a third, taking into account nearest-to-integer + instead of averaging. + ================== + */ +void SnapWeldVectorAccu( vec3_accu_t a, vec3_accu_t b, vec3_accu_t out ){ + // I'm just preserving what I think was the intended logic of the original + // SnapWeldVector(). I'm not actually sure where this function should even + // be used. I'd like to know which kinds of problems this function addresses. + + // TODO: I thought we're snapping all coordinates to nearest 1/8 unit? + // So what is natural about snapping to the nearest integer? Maybe we should + // be snapping to the nearest 1/8 unit instead? + + int i; + vec_accu_t ai, bi, ad, bd; + + if ( a == NULL || b == NULL || out == NULL ) { + Error( "SnapWeldVectorAccu: NULL argument" ); + } + + for ( i = 0; i < 3; i++ ) + { + ai = Q_rintAccu( a[i] ); + bi = Q_rintAccu( b[i] ); + ad = fabs( ai - a[i] ); + bd = fabs( bi - b[i] ); + + if ( ad < bd ) { + if ( ad < SNAP_EPSILON ) { + out[i] = ai; + } + else{out[i] = a[i]; } + } + else + { + if ( bd < SNAP_EPSILON ) { + out[i] = bi; + } + else{out[i] = b[i]; } + } + } +} + + + +/* + FixWinding() - ydnar + removes degenerate edges from a winding + returns qtrue if the winding is valid + */ + +#define DEGENERATE_EPSILON 0.1 + +qboolean FixWinding( winding_t *w ){ + qboolean valid = qtrue; + int i, j, k; + vec3_t vec; + float dist; + + + /* dummy check */ + if ( !w ) { + return qfalse; + } + + /* check all verts */ + for ( i = 0; i < w->numpoints; i++ ) + { + /* don't remove points if winding is a triangle */ + if ( w->numpoints == 3 ) { + return valid; + } + + /* get second point index */ + j = ( i + 1 ) % w->numpoints; + + /* degenerate edge? */ + VectorSubtract( w->p[ i ], w->p[ j ], vec ); + dist = VectorLength( vec ); + if ( dist < DEGENERATE_EPSILON ) { + valid = qfalse; + //Sys_FPrintf( SYS_VRB, "WARNING: Degenerate winding edge found, fixing...\n" ); + + /* create an average point (ydnar 2002-01-26: using nearest-integer weld preference) */ + SnapWeldVector( w->p[ i ], w->p[ j ], vec ); + VectorCopy( vec, w->p[ i ] ); + //VectorAdd( w->p[ i ], w->p[ j ], vec ); + //VectorScale( vec, 0.5, w->p[ i ] ); + + /* move the remaining verts */ + for ( k = i + 2; k < w->numpoints; k++ ) + { + VectorCopy( w->p[ k ], w->p[ k - 1 ] ); + } + w->numpoints--; + } + } + + /* one last check and return */ + if ( w->numpoints < 3 ) { + valid = qfalse; + } + return valid; +} + +/* + ================== + FixWindingAccu + + Removes degenerate edges (edges that are too short) from a winding. + Returns qtrue if the winding has been altered by this function. + Returns qfalse if the winding is untouched by this function. + + It's advised that you check the winding after this function exits to make + sure it still has at least 3 points. If that is not the case, the winding + cannot be considered valid. The winding may degenerate to one or two points + if the some of the winding's points are close together. + ================== + */ +qboolean FixWindingAccu( winding_accu_t *w ){ + int i, j, k; + vec3_accu_t vec; + vec_accu_t dist; + qboolean done, altered; + + if ( w == NULL ) { + Error( "FixWindingAccu: NULL argument" ); + } + + altered = qfalse; + + while ( qtrue ) + { + if ( w->numpoints < 2 ) { + break; // Don't remove the only remaining point. + } + done = qtrue; + for ( i = 0; i < w->numpoints; i++ ) + { + j = ( ( ( i + 1 ) == w->numpoints ) ? 0 : ( i + 1 ) ); + + VectorSubtractAccu( w->p[i], w->p[j], vec ); + dist = VectorLengthAccu( vec ); + if ( dist < DEGENERATE_EPSILON ) { + // TODO: I think the "snap weld vector" was written before + // some of the math precision fixes, and its purpose was + // probably to address math accuracy issues. We can think + // about changing the logic here. Maybe once plane distance + // gets 64 bits, we can look at it then. + SnapWeldVectorAccu( w->p[i], w->p[j], vec ); + VectorCopyAccu( vec, w->p[i] ); + for ( k = j + 1; k < w->numpoints; k++ ) + { + VectorCopyAccu( w->p[k], w->p[k - 1] ); + } + w->numpoints--; + altered = qtrue; + // The only way to finish off fixing the winding consistently and + // accurately is by fixing the winding all over again. For example, + // the point at index i and the point at index i-1 could now be + // less than the epsilon distance apart. There are too many special + // case problems we'd need to handle if we didn't start from the + // beginning. + done = qfalse; + break; // This will cause us to return to the "while" loop. + } + } + if ( done ) { + break; + } + } + + return altered; +} + + +/* + CreateBrushWindings() + makes basewindigs for sides and mins/maxs for the brush + returns false if the brush doesn't enclose a valid volume + */ + +qboolean CreateBrushWindings( brush_t *brush ){ + int i, j; +#if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES + winding_accu_t *w; +#else + winding_t *w; +#endif + side_t *side; + plane_t *plane; + + + /* walk the list of brush sides */ + for ( i = 0; i < brush->numsides; i++ ) + { + /* get side and plane */ + side = &brush->sides[ i ]; + plane = &mapplanes[ side->planenum ]; + + /* make huge winding */ +#if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES + w = BaseWindingForPlaneAccu( plane->normal, plane->dist ); +#else + w = BaseWindingForPlane( plane->normal, plane->dist ); +#endif + + /* walk the list of brush sides */ + for ( j = 0; j < brush->numsides && w != NULL; j++ ) + { + if ( i == j ) { + continue; + } + if ( brush->sides[ j ].planenum == ( brush->sides[ i ].planenum ^ 1 ) ) { + continue; /* back side clipaway */ + } + if ( brush->sides[ j ].bevel ) { + continue; + } + plane = &mapplanes[ brush->sides[ j ].planenum ^ 1 ]; +#if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES + ChopWindingInPlaceAccu( &w, plane->normal, plane->dist, 0 ); +#else + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); // CLIP_EPSILON ); +#endif + + /* ydnar: fix broken windings that would generate trifans */ +#if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES + // I think it's better to FixWindingAccu() once after we chop with all planes + // so that error isn't multiplied. There is nothing natural about welding + // the points unless they are the final endpoints. ChopWindingInPlaceAccu() + // is able to handle all kinds of degenerate windings. +#else + FixWinding( w ); +#endif + } + + /* set side winding */ +#if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES + if ( w != NULL ) { + FixWindingAccu( w ); + if ( w->numpoints < 3 ) { + FreeWindingAccu( w ); + w = NULL; + } + } + side->winding = ( w ? CopyWindingAccuToRegular( w ) : NULL ); + if ( w ) { + FreeWindingAccu( w ); + } +#else + side->winding = w; +#endif + } + + /* find brush bounds */ + return BoundBrush( brush ); +} + + + + +/* + ================== + BrushFromBounds + + Creates a new axial brush + ================== + */ +brush_t *BrushFromBounds( vec3_t mins, vec3_t maxs ){ + brush_t *b; + int i; + vec3_t normal; + vec_t dist; + + b = AllocBrush( 6 ); + b->numsides = 6; + for ( i = 0 ; i < 3 ; i++ ) + { + VectorClear( normal ); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = FindFloatPlane( normal, dist, 1, (vec3_t*) &maxs ); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3 + i].planenum = FindFloatPlane( normal, dist, 1, (vec3_t*) &mins ); + } + + CreateBrushWindings( b ); + + return b; +} + +/* + ================== + BrushVolume + + ================== + */ +vec_t BrushVolume( brush_t *brush ){ + int i; + winding_t *w; + vec3_t corner; + vec_t d, area, volume; + plane_t *plane; + + if ( !brush ) { + return 0; + } + + // grab the first valid point as the corner + + w = NULL; + for ( i = 0 ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( w ) { + break; + } + } + if ( !w ) { + return 0; + } + VectorCopy( w->p[0], corner ); + + // make tetrahedrons to all other faces + + volume = 0; + for ( ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + plane = &mapplanes[brush->sides[i].planenum]; + d = -( DotProduct( corner, plane->normal ) - plane->dist ); + area = WindingArea( w ); + volume += d * area; + } + + volume /= 3; + return volume; +} + + + +/* + WriteBSPBrushMap() + writes a map with the split bsp brushes + */ + +void WriteBSPBrushMap( char *name, brush_t *list ){ + FILE *f; + side_t *s; + int i; + winding_t *w; + + + /* note it */ + Sys_Printf( "Writing %s\n", name ); + + /* open the map file */ + f = fopen( name, "wb" ); + if ( f == NULL ) { + Error( "Can't write %s\b", name ); + } + + fprintf( f, "{\n\"classname\" \"worldspawn\"\n" ); + + for ( ; list ; list = list->next ) + { + fprintf( f, "{\n" ); + for ( i = 0,s = list->sides ; i < list->numsides ; i++,s++ ) + { + // TODO: See if we can use a smaller winding to prevent resolution loss. + // Is WriteBSPBrushMap() used only to decompile maps? + w = BaseWindingForPlane( mapplanes[s->planenum].normal, mapplanes[s->planenum].dist ); + + fprintf( f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2] ); + fprintf( f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2] ); + fprintf( f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2] ); + + fprintf( f, "notexture 0 0 0 1 1\n" ); + FreeWinding( w ); + } + fprintf( f, "}\n" ); + } + fprintf( f, "}\n" ); + + fclose( f ); + +} + + + +/* + FilterBrushIntoTree_r() + adds brush reference to any intersecting bsp leafnode + */ + +int FilterBrushIntoTree_r( brush_t *b, node_t *node ){ + brush_t *front, *back; + int c; + + + /* dummy check */ + if ( b == NULL ) { + return 0; + } + + /* add it to the leaf list */ + if ( node->planenum == PLANENUM_LEAF ) { + /* something somewhere is hammering brushlist */ + b->next = node->brushlist; + node->brushlist = b; + + /* classify the leaf by the structural brush */ + if ( !b->detail ) { + if ( b->opaque ) { + node->opaque = qtrue; + node->areaportal = qfalse; + } + else if ( b->compileFlags & C_AREAPORTAL ) { + if ( !node->opaque ) { + node->areaportal = qtrue; + } + } + } + + return 1; + } + + /* split it by the node plane */ + c = b->numsides; + SplitBrush( b, node->planenum, &front, &back ); + FreeBrush( b ); + + c = 0; + c += FilterBrushIntoTree_r( front, node->children[ 0 ] ); + c += FilterBrushIntoTree_r( back, node->children[ 1 ] ); + + return c; +} + + + +/* + FilterDetailBrushesIntoTree + fragment all the detail brushes into the structural leafs + */ + +void FilterDetailBrushesIntoTree( entity_t *e, tree_t *tree ){ + brush_t *b, *newb; + int r; + int c_unique, c_clusters; + int i; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- FilterDetailBrushesIntoTree ---\n" ); + + /* walk the list of brushes */ + c_unique = 0; + c_clusters = 0; + for ( b = e->brushes; b; b = b->next ) + { + if ( !b->detail ) { + continue; + } + c_unique++; + newb = CopyBrush( b ); + r = FilterBrushIntoTree_r( newb, tree->headnode ); + c_clusters += r; + + /* mark all sides as visible so drawsurfs are created */ + if ( r ) { + for ( i = 0; i < b->numsides; i++ ) + { + if ( b->sides[ i ].winding ) { + b->sides[ i ].visible = qtrue; + } + } + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_unique ); + Sys_FPrintf( SYS_VRB, "%9d cluster references\n", c_clusters ); +} + +/* + ===================== + FilterStructuralBrushesIntoTree + + Mark the leafs as opaque and areaportals + ===================== + */ +void FilterStructuralBrushesIntoTree( entity_t *e, tree_t *tree ) { + brush_t *b, *newb; + int r; + int c_unique, c_clusters; + int i; + + Sys_FPrintf( SYS_VRB, "--- FilterStructuralBrushesIntoTree ---\n" ); + + c_unique = 0; + c_clusters = 0; + for ( b = e->brushes ; b ; b = b->next ) { + if ( b->detail ) { + continue; + } + c_unique++; + newb = CopyBrush( b ); + r = FilterBrushIntoTree_r( newb, tree->headnode ); + c_clusters += r; + + // mark all sides as visible so drawsurfs are created + if ( r ) { + for ( i = 0 ; i < b->numsides ; i++ ) { + if ( b->sides[i].winding ) { + b->sides[i].visible = qtrue; + } + } + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d structural brushes\n", c_unique ); + Sys_FPrintf( SYS_VRB, "%9d cluster references\n", c_clusters ); +} + + + +/* + ================ + AllocTree + ================ + */ +tree_t *AllocTree( void ){ + tree_t *tree; + + tree = safe_malloc( sizeof( *tree ) ); + memset( tree, 0, sizeof( *tree ) ); + ClearBounds( tree->mins, tree->maxs ); + + return tree; +} + +/* + ================ + AllocNode + ================ + */ +node_t *AllocNode( void ){ + node_t *node; + + node = safe_malloc( sizeof( *node ) ); + memset( node, 0, sizeof( *node ) ); + + return node; +} + + +/* + ================ + WindingIsTiny + + Returns true if the winding would be crunched out of + existance by the vertex snapping. + ================ + */ +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny( winding_t *w ){ +/* + if (WindingArea (w) < 1) + return qtrue; + return qfalse; + */ + int i, j; + vec_t len; + vec3_t delta; + int edges; + + edges = 0; + for ( i = 0 ; i < w->numpoints ; i++ ) + { + j = i == w->numpoints - 1 ? 0 : i + 1; + VectorSubtract( w->p[j], w->p[i], delta ); + len = VectorLength( delta ); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return qfalse; + } + } + } + return qtrue; +} + +/* + ================ + WindingIsHuge + + Returns true if the winding still has one of the points + from basewinding for plane + ================ + */ +qboolean WindingIsHuge( winding_t *w ){ + int i, j; + + for ( i = 0 ; i < w->numpoints ; i++ ) + { + for ( j = 0 ; j < 3 ; j++ ) + if ( w->p[i][j] <= MIN_WORLD_COORD || w->p[i][j] >= MAX_WORLD_COORD ) { + return qtrue; + } + } + return qfalse; +} + +//============================================================ + +/* + ================== + BrushMostlyOnSide + + ================== + */ +int BrushMostlyOnSide( brush_t *brush, plane_t *plane ){ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for ( i = 0 ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + for ( j = 0 ; j < w->numpoints ; j++ ) + { + d = DotProduct( w->p[j], plane->normal ) - plane->dist; + if ( d > max ) { + max = d; + side = PSIDE_FRONT; + } + if ( -d > max ) { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} + + + +/* + SplitBrush() + generates two new brushes, leaving the original unchanged + */ + +void SplitBrush( brush_t *brush, int planenum, brush_t **front, brush_t **back ){ + brush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + + *front = NULL; + *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for ( i = 0 ; i < brush->numsides ; i++ ) + { + w = brush->sides[i].winding; + if ( !w ) { + continue; + } + for ( j = 0 ; j < w->numpoints ; j++ ) + { + d = DotProduct( w->p[j], plane->normal ) - plane->dist; + if ( d > 0 && d > d_front ) { + d_front = d; + } + if ( d < 0 && d < d_back ) { + d_back = d; + } + } + } + + if ( d_front < 0.1 ) { // PLANESIDE_EPSILON) + // only on back + *back = CopyBrush( brush ); + return; + } + + if ( d_back > -0.1 ) { // PLANESIDE_EPSILON) + // only on front + *front = CopyBrush( brush ); + return; + } + + // create a new winding from the split plane + w = BaseWindingForPlane( plane->normal, plane->dist ); + for ( i = 0 ; i < brush->numsides && w ; i++ ) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace( &w, plane2->normal, plane2->dist, 0 ); // PLANESIDE_EPSILON); + } + + if ( !w || WindingIsTiny( w ) ) { // the brush isn't really split + int side; + + side = BrushMostlyOnSide( brush, plane ); + if ( side == PSIDE_FRONT ) { + *front = CopyBrush( brush ); + } + if ( side == PSIDE_BACK ) { + *back = CopyBrush( brush ); + } + return; + } + + if ( WindingIsHuge( w ) ) { + Sys_FPrintf( SYS_VRB,"WARNING: huge winding\n" ); + } + + midwinding = w; + + // split it for real + + for ( i = 0 ; i < 2 ; i++ ) + { + b[i] = AllocBrush( brush->numsides + 1 ); + memcpy( b[i], brush, sizeof( brush_t ) - sizeof( brush->sides ) ); + b[i]->numsides = 0; + b[i]->next = NULL; + b[i]->original = brush->original; + } + + // split all the current windings + + for ( i = 0 ; i < brush->numsides ; i++ ) + { + s = &brush->sides[i]; + w = s->winding; + if ( !w ) { + continue; + } + /* strict, in parallel case we get the face back because it also is the midwinding */ + ClipWindingEpsilonStrict( w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1] ); + for ( j = 0 ; j < 2 ; j++ ) + { + if ( !cw[j] ) { + continue; + } + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; + cs->winding = cw[j]; + } + } + + + // see if we have valid polygons on both sides + for ( i = 0 ; i < 2 ; i++ ) + { + if ( b[i]->numsides < 3 || !BoundBrush( b[i] ) ) { + if ( b[i]->numsides >= 3 ) { + Sys_FPrintf( SYS_VRB,"bogus brush after clip\n" ); + } + FreeBrush( b[i] ); + b[i] = NULL; + } + } + + if ( !( b[0] && b[1] ) ) { + if ( !b[0] && !b[1] ) { + Sys_FPrintf( SYS_VRB,"split removed brush\n" ); + } + else{ + Sys_FPrintf( SYS_VRB,"split not on both sides\n" ); + } + if ( b[0] ) { + FreeBrush( b[0] ); + *front = CopyBrush( brush ); + } + if ( b[1] ) { + FreeBrush( b[1] ); + *back = CopyBrush( brush ); + } + return; + } + + // add the midwinding to both sides + for ( i = 0 ; i < 2 ; i++ ) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum ^ i ^ 1; + cs->shaderInfo = NULL; + if ( i == 0 ) { + cs->winding = CopyWinding( midwinding ); + } + else{ + cs->winding = midwinding; + } + } + + { + vec_t v1; + int i; + + + for ( i = 0 ; i < 2 ; i++ ) + { + v1 = BrushVolume( b[i] ); + if ( v1 < 1.0 ) { + FreeBrush( b[i] ); + b[i] = NULL; + // Sys_FPrintf (SYS_VRB,"tiny volume after clip\n"); + } + } + } + + *front = b[0]; + *back = b[1]; +} diff --git a/tools/vmap/brush_primit.c b/tools/vmap/brush_primit.c new file mode 100644 index 0000000..0e29297 --- /dev/null +++ b/tools/vmap/brush_primit.c @@ -0,0 +1,83 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BRUSH_PRIMIT_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* ------------------------------------------------------------------------------- + + functions + + ------------------------------------------------------------------------------- */ + +/* + ComputeAxisBase() + computes the base texture axis for brush primitive texturing + note: ComputeAxisBase here and in editor code must always BE THE SAME! + warning: special case behaviour of atan2( y, x ) <-> atan( y / x ) might not be the same everywhere when x == 0 + rotation by (0,RotY,RotZ) assigns X to normal + */ + +void ComputeAxisBase( vec3_t normal, vec3_t texX, vec3_t texY ){ + vec_t RotY, RotZ; + + + /* do some cleaning */ + if ( fabs( normal[ 0 ] ) < 1e-6 ) { + normal[ 0 ] = 0.0f; + } + if ( fabs( normal[ 1 ] ) < 1e-6 ) { + normal[ 1 ] = 0.0f; + } + if ( fabs( normal[ 2 ] ) < 1e-6 ) { + normal[ 2 ] = 0.0f; + } + + /* compute the two rotations around y and z to rotate x to normal */ + RotY = -atan2( normal[ 2 ], sqrt( normal[ 1 ] * normal[ 1 ] + normal[ 0 ] * normal[ 0 ] ) ); + RotZ = atan2( normal[ 1 ], normal[ 0 ] ); + + /* rotate (0,1,0) and (0,0,1) to compute texX and texY */ + texX[ 0 ] = -sin( RotZ ); + texX[ 1 ] = cos( RotZ ); + texX[ 2 ] = 0; + + /* the texY vector is along -z (t texture coorinates axis) */ + texY[ 0 ] = -sin( RotY ) * cos( RotZ ); + texY[ 1 ] = -sin( RotY ) * sin( RotZ ); + texY[ 2 ] = -cos( RotY ); +} diff --git a/tools/vmap/bsp.c b/tools/vmap/bsp.c new file mode 100644 index 0000000..deed375 --- /dev/null +++ b/tools/vmap/bsp.c @@ -0,0 +1,1033 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSP_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* ------------------------------------------------------------------------------- + + functions + + ------------------------------------------------------------------------------- */ + + +/* + SetCloneModelNumbers() - ydnar + sets the model numbers for brush entities + */ + +static void SetCloneModelNumbers( void ){ + int i, j; + int models; + char modelValue[ 10 ]; + const char *value, *value2, *value3; + + + /* start with 1 (worldspawn is model 0) */ + models = 1; + for ( i = 1; i < numEntities; i++ ) + { + /* only entities with brushes or patches get a model number */ + if ( entities[ i ].brushes == NULL && entities[ i ].patches == NULL ) { + continue; + } + + /* is this a clone? */ + value = ValueForKey( &entities[ i ], "_ins" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ i ], "_instance" ); + } + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ i ], "_clone" ); + } + if ( value[ 0 ] != '\0' ) { + continue; + } + + /* add the model key */ + sprintf( modelValue, "*%d", models ); + SetKeyValue( &entities[ i ], "model", modelValue ); + + /* increment model count */ + models++; + } + + /* fix up clones */ + for ( i = 1; i < numEntities; i++ ) + { + /* only entities with brushes or patches get a model number */ + if ( entities[ i ].brushes == NULL && entities[ i ].patches == NULL ) { + continue; + } + + /* is this a clone? */ + value = ValueForKey( &entities[ i ], "_ins" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ i ], "_instance" ); + } + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ i ], "_clone" ); + } + if ( value[ 0 ] == '\0' ) { + continue; + } + + /* find an entity with matching clone name */ + for ( j = 0; j < numEntities; j++ ) + { + /* is this a clone parent? */ + value2 = ValueForKey( &entities[ j ], "_clonename" ); + if ( value2[ 0 ] == '\0' ) { + continue; + } + + /* do they match? */ + if ( strcmp( value, value2 ) == 0 ) { + /* get the model num */ + value3 = ValueForKey( &entities[ j ], "model" ); + if ( value3[ 0 ] == '\0' ) { + Sys_FPrintf( SYS_WRN, "WARNING: Cloned entity %s referenced entity without model\n", value2 ); + continue; + } + models = atoi( &value2[ 1 ] ); + + /* add the model key */ + sprintf( modelValue, "*%d", models ); + SetKeyValue( &entities[ i ], "model", modelValue ); + + /* nuke the brushes/patches for this entity (fixme: leak!) */ + entities[ i ].brushes = NULL; + entities[ i ].patches = NULL; + } + } + } +} + + + +/* + FixBrushSides() - ydnar + matches brushsides back to their appropriate drawsurface and shader + */ + +static void FixBrushSides( entity_t *e ){ + int i; + mapDrawSurface_t *ds; + sideRef_t *sideRef; + bspBrushSide_t *side; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- FixBrushSides ---\n" ); + + /* walk list of drawsurfaces */ + for ( i = e->firstDrawSurf; i < numMapDrawSurfs; i++ ) + { + /* get surface and try to early out */ + ds = &mapDrawSurfs[ i ]; + if ( ds->outputNum < 0 ) { + continue; + } + + /* walk sideref list */ + for ( sideRef = ds->sideRef; sideRef != NULL; sideRef = sideRef->next ) + { + /* get bsp brush side */ + if ( sideRef->side == NULL || sideRef->side->outputNum < 0 ) { + continue; + } + side = &bspBrushSides[ sideRef->side->outputNum ]; + + /* set drawsurface */ + side->surfaceNum = ds->outputNum; + //% Sys_FPrintf( SYS_VRB, "DS: %7d Side: %7d ", ds->outputNum, sideRef->side->outputNum ); + + /* set shader */ + if ( strcmp( bspShaders[ side->shaderNum ].shader, ds->shaderInfo->shader ) ) { + //% Sys_FPrintf( SYS_VRB, "Remapping %s to %s\n", bspShaders[ side->shaderNum ].shader, ds->shaderInfo->shader ); + side->shaderNum = EmitShader( ds->shaderInfo->shader, &ds->shaderInfo->contentFlags, &ds->shaderInfo->surfaceFlags ); + } + } + } +} + + + +/* + ProcessWorldModel() + creates a full bsp + surfaces for the worldspawn entity + */ + +void ProcessWorldModel( const char *portalFilePath, const char *lineFilePath ){ + int i, s; + entity_t *e; + tree_t *tree; + face_t *faces; + qboolean ignoreLeaks, leaked; + xmlNodePtr polyline, leaknode; + char level[ 2 ], shader[ 1024 ]; + const char *value; + int leakStatus; + + /* sets integer blockSize from worldspawn "_blocksize" key if it exists */ + value = ValueForKey( &entities[ 0 ], "_blocksize" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ 0 ], "blocksize" ); + } + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ 0 ], "chopsize" ); /* sof2 */ + } + if ( value[ 0 ] != '\0' ) { + /* scan 3 numbers */ + s = sscanf( value, "%d %d %d", &blockSize[ 0 ], &blockSize[ 1 ], &blockSize[ 2 ] ); + + /* handle legacy case */ + if ( s == 1 ) { + blockSize[ 1 ] = blockSize[ 0 ]; + blockSize[ 2 ] = blockSize[ 0 ]; + } + } + Sys_Printf( "block size = { %d %d %d }\n", blockSize[ 0 ], blockSize[ 1 ], blockSize[ 2 ] ); + + /* sof2: ignore leaks? */ + value = ValueForKey( &entities[ 0 ], "_ignoreleaks" ); /* ydnar */ + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ 0 ], "ignoreleaks" ); + } + if ( value[ 0 ] == '1' ) { + ignoreLeaks = qtrue; + } + else{ + ignoreLeaks = qfalse; + } + + /* begin worldspawn model */ + BeginModel(); + e = &entities[ 0 ]; + e->firstDrawSurf = 0; + + /* ydnar: gs mods */ + ClearMetaTriangles(); + + /* check for patches with adjacent edges that need to lod together */ + PatchMapDrawSurfs( e ); + + /* build an initial bsp tree using all of the sides of all of the structural brushes */ + faces = MakeStructuralBSPFaceList( entities[ 0 ].brushes ); + tree = FaceBSP( faces ); + MakeTreePortals( tree ); + FilterStructuralBrushesIntoTree( e, tree ); + + /* see if the bsp is completely enclosed */ + leakStatus = FloodEntities( tree ); + if ( ignoreLeaks ) { + if ( leakStatus == FLOODENTITIES_LEAKED ) { + leakStatus = FLOODENTITIES_GOOD; + } + } + + if ( leakStatus == FLOODENTITIES_GOOD ) { + leaked = qfalse; + } + else + { + leaked = qtrue; + + Sys_FPrintf( SYS_NOXML, "**********************\n" ); + Sys_FPrintf( SYS_NOXML, "******* leaked *******\n" ); + Sys_FPrintf( SYS_NOXML, "**********************\n" ); + polyline = LeakFile( tree, lineFilePath ); + leaknode = xmlNewNode( NULL, (xmlChar*)"message" ); + xmlNodeSetContent( leaknode, (xmlChar*)"MAP LEAKED\n" ); + xmlAddChild( leaknode, polyline ); + level[0] = (int) '0' + SYS_ERR; + level[1] = 0; + xmlSetProp( leaknode, (xmlChar*)"level", (xmlChar*) &level ); + xml_SendNode( leaknode ); + + /* abort by default, we should never leak */ + if ( !leaktest ) { + Sys_Printf( "--- MAP LEAKED, ABORTING LEAKTEST ---\n" ); + exit( 0 ); + } + } + + if ( leakStatus != FLOODENTITIES_EMPTY ) { /* if no entities exist, this would accidentally the whole map, and that IS bad */ + /* rebuild a better bsp tree using only the sides that are visible from the inside */ + FillOutside( tree->headnode ); + + /* chop the sides to the convex hull of their visible fragments, giving us the smallest polygons */ + ClipSidesIntoTree( e, tree ); + + /* build a visible face tree (same thing as the initial bsp tree but after reducing the faces) */ + faces = MakeVisibleBSPFaceList( entities[ 0 ].brushes ); + FreeTree( tree ); + tree = FaceBSP( faces ); + MakeTreePortals( tree ); + FilterStructuralBrushesIntoTree( e, tree ); + + /* ydnar: flood again for skybox */ + if ( skyboxPresent ) { + FloodEntities( tree ); + } + } + + /* save out information for visibility processing */ + NumberClusters( tree ); + if ( !leaked ) { + WritePortalFile( tree, portalFilePath ); + } + + /* flood from entities */ + FloodAreas( tree ); + + /* create drawsurfs for triangle models */ + AddTriangleModels( e ); + + /* create drawsurfs for surface models */ + AddEntitySurfaceModels( e ); + + /* generate bsp brushes from map brushes */ + EmitBrushes( e->brushes, &e->firstBrush, &e->numBrushes ); + + /* add references to the detail brushes */ + FilterDetailBrushesIntoTree( e, tree ); + + /* drawsurfs that cross fog boundaries will need to be split along the fog boundary */ + if ( !nofog ) { + FogDrawSurfaces( e ); + } + + /* subdivide each drawsurf as required by shader tesselation */ + if ( !nosubdivide ) { + SubdivideFaceSurfaces( e, tree ); + } + + /* add in any vertexes required to fix t-junctions */ + if ( !notjunc ) { + FixTJunctions( e ); + } + + /* ydnar: classify the surfaces */ + ClassifyEntitySurfaces( e ); + + /* ydnar: project decals */ + MakeEntityDecals( e ); + + /* ydnar: meta surfaces */ + MakeEntityMetaTriangles( e ); + SmoothMetaTriangles(); + FixMetaTJunctions(); + MergeMetaTriangles(); + + /* ydnar: debug portals */ + if ( debugPortals ) { + MakeDebugPortalSurfs( tree ); + } + + /* ydnar: fog hull */ + value = ValueForKey( &entities[ 0 ], "_foghull" ); + if ( value[ 0 ] != '\0' ) { + sprintf( shader, "textures/%s", value ); + MakeFogHullSurfs( e, tree, shader ); + } + + /* ydnar: bug 645: do flares for lights */ + for ( i = 0; i < numEntities && emitFlares; i++ ) + { + entity_t *light, *target; + const char *value, *flareShader; + vec3_t origin, targetOrigin, normal, color; + int lightStyle; + + + /* get light */ + light = &entities[ i ]; + value = ValueForKey( light, "classname" ); + if ( !strcmp( value, "light" ) ) { + /* get flare shader */ + flareShader = ValueForKey( light, "_flareshader" ); + value = ValueForKey( light, "_flare" ); + if ( flareShader[ 0 ] != '\0' || value[ 0 ] != '\0' ) { + /* get specifics */ + GetVectorForKey( light, "origin", origin ); + GetVectorForKey( light, "_color", color ); + lightStyle = IntForKey( light, "_style" ); + if ( lightStyle == 0 ) { + lightStyle = IntForKey( light, "style" ); + } + + /* handle directional spotlights */ + value = ValueForKey( light, "target" ); + if ( value[ 0 ] != '\0' ) { + /* get target light */ + target = FindTargetEntity( value ); + if ( target != NULL ) { + GetVectorForKey( target, "origin", targetOrigin ); + VectorSubtract( targetOrigin, origin, normal ); + VectorNormalize( normal, normal ); + } + } + else{ + //% VectorClear( normal ); + VectorSet( normal, 0, 0, -1 ); + } + + if ( colorsRGB ) { + color[0] = Image_LinearFloatFromsRGBFloat( color[0] ); + color[1] = Image_LinearFloatFromsRGBFloat( color[1] ); + color[2] = Image_LinearFloatFromsRGBFloat( color[2] ); + } + + /* create the flare surface (note shader defaults automatically) */ + DrawSurfaceForFlare( mapEntityNum, origin, normal, color, flareShader, lightStyle ); + } + } + } + + /* add references to the final drawsurfs in the apropriate clusters */ + FilterDrawsurfsIntoTree( e, tree ); + + /* match drawsurfaces back to original brushsides (sof2) */ + FixBrushSides( e ); + + /* finish */ + EndModel( e, tree->headnode ); + FreeTree( tree ); +} + + + +/* + ProcessSubModel() + creates bsp + surfaces for other brush models + */ + +void ProcessSubModel( void ){ + entity_t *e; + tree_t *tree; + brush_t *b, *bc; + node_t *node; + + + /* start a brush model */ + BeginModel(); + e = &entities[ mapEntityNum ]; + e->firstDrawSurf = numMapDrawSurfs; + + /* ydnar: gs mods */ + ClearMetaTriangles(); + + /* check for patches with adjacent edges that need to lod together */ + PatchMapDrawSurfs( e ); + + /* allocate a tree */ + node = AllocNode(); + node->planenum = PLANENUM_LEAF; + tree = AllocTree(); + tree->headnode = node; + + /* add the sides to the tree */ + ClipSidesIntoTree( e, tree ); + + /* ydnar: create drawsurfs for triangle models */ + AddTriangleModels( e ); + + /* create drawsurfs for surface models */ + AddEntitySurfaceModels( e ); + + /* generate bsp brushes from map brushes */ + EmitBrushes( e->brushes, &e->firstBrush, &e->numBrushes ); + + /* just put all the brushes in headnode */ + for ( b = e->brushes; b; b = b->next ) + { + bc = CopyBrush( b ); + bc->next = node->brushlist; + node->brushlist = bc; + } + + /* subdivide each drawsurf as required by shader tesselation */ + if ( !nosubdivide ) { + SubdivideFaceSurfaces( e, tree ); + } + + /* add in any vertexes required to fix t-junctions */ + if ( !notjunc ) { + FixTJunctions( e ); + } + + /* ydnar: classify the surfaces and project lightmaps */ + ClassifyEntitySurfaces( e ); + + /* ydnar: project decals */ + MakeEntityDecals( e ); + + /* ydnar: meta surfaces */ + MakeEntityMetaTriangles( e ); + SmoothMetaTriangles(); + FixMetaTJunctions(); + MergeMetaTriangles(); + + /* add references to the final drawsurfs in the apropriate clusters */ + FilterDrawsurfsIntoTree( e, tree ); + + /* match drawsurfaces back to original brushsides (sof2) */ + FixBrushSides( e ); + + /* finish */ + EndModel( e, node ); + FreeTree( tree ); +} + + + +/* + ProcessModels() + process world + other models into the bsp + */ + +void ProcessModels( const char *portalFilePath, const char *lineFilePath ){ + qboolean oldVerbose; + entity_t *entity; + + + /* preserve -v setting */ + oldVerbose = verbose; + + /* start a new bsp */ + BeginBSPFile(); + + /* create map fogs */ + CreateMapFogs(); + + /* find the envmap points */ + CreateMapCubemaps(); + + /* walk entity list */ + for ( mapEntityNum = 0; mapEntityNum < numEntities; mapEntityNum++ ) + { + /* get entity */ + entity = &entities[ mapEntityNum ]; + if ( entity->brushes == NULL && entity->patches == NULL ) { + continue; + } + + /* process the model */ + Sys_FPrintf( SYS_VRB, "############### model %i ###############\n", numBSPModels ); + if ( mapEntityNum == 0 ) { + ProcessWorldModel(portalFilePath, lineFilePath); + } + else{ + ProcessSubModel(); + } + + /* potentially turn off the deluge of text */ + verbose = verboseEntities; + } + + /* restore -v setting */ + verbose = oldVerbose; + + /* write fogs */ + EmitFogs(); + + /* vortex: emit meta stats */ + EmitMetaStats(); +} + + + +/* + OnlyEnts() + this is probably broken unless teamed with a radiant version that preserves entity order + */ + +void OnlyEnts( const char *BSPFilePath ){ + char save_cmdline[1024], save_version[1024], save_gridsize[1024]; + const char *p; + + /* note it */ + Sys_Printf( "--- OnlyEnts ---\n" ); + + LoadBSPFile( BSPFilePath ); + + ParseEntities(); + p = ValueForKey( &entities[0], "_q3map2_cmdline" ); + strncpy( save_cmdline, p, sizeof( save_cmdline ) ); + save_cmdline[sizeof( save_cmdline ) - 1] = 0; + p = ValueForKey( &entities[0], "_q3map2_version" ); + strncpy( save_version, p, sizeof( save_version ) ); + save_version[sizeof( save_version ) - 1] = 0; + p = ValueForKey( &entities[0], "gridsize" ); + strncpy( save_gridsize, p, sizeof( save_gridsize ) ); + save_gridsize[sizeof( save_gridsize ) - 1] = 0; + + numEntities = 0; + + LoadShaderInfo(); + LoadMapFile( name, qfalse, qfalse ); + SetModelNumbers(); + SetLightStyles(); + + if ( *save_cmdline ) { + SetKeyValue( &entities[0], "_q3map2_cmdline", save_cmdline ); + } + if ( *save_version ) { + SetKeyValue( &entities[0], "_q3map2_version", save_version ); + } + if ( *save_gridsize ) { + SetKeyValue( &entities[0], "gridsize", save_gridsize ); + } + + numBSPEntities = numEntities; + UnparseEntities(); + + WriteBSPFile( BSPFilePath ); +} + + + +/* + BSPMain() - ydnar + handles creation of a bsp from a map file + */ + +int BSPMain( int argc, char **argv ){ + int i; + char path[ 1024 ], tempSource[ 1024 ]; + qboolean onlyents = qfalse; + char BSPFilePath [ 1024 ]; + char lineFilePath [ 1024 ]; + char portalFilePath [ 1024 ]; + char surfaceFilePath [ 1024 ]; + BSPFilePath[0] = 0; + lineFilePath[0] = 0; + portalFilePath[0] = 0; + surfaceFilePath[0] = 0; + + + /* note it */ + Sys_Printf( "--- BSP ---\n" ); + + SetDrawSurfacesBuffer(); + mapDrawSurfs = safe_malloc( sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); + memset( mapDrawSurfs, 0, sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); + numMapDrawSurfs = 0; + + tempSource[ 0 ] = '\0'; + globalCelShader[0] = 0; + + /* set standard game flags */ + maxSurfaceVerts = game->maxSurfaceVerts; + maxSurfaceIndexes = game->maxSurfaceIndexes; + emitFlares = game->emitFlares; + texturesRGB = game->texturesRGB; + colorsRGB = game->colorsRGB; + + /* process arguments */ + for ( i = 1; i < ( argc - 1 ); i++ ) + { + if ( !strcmp( argv[ i ], "-onlyents" ) ) { + Sys_Printf( "Running entity-only compile\n" ); + onlyents = qtrue; + } + else if ( !strcmp( argv[ i ], "-tempname" ) ) { + strcpy( tempSource, argv[ ++i ] ); + } + else if ( !strcmp( argv[ i ], "-tmpout" ) ) { + strcpy( outbase, "/tmp" ); + } + else if ( !strcmp( argv[ i ], "-nowater" ) ) { + Sys_Printf( "Disabling water\n" ); + nowater = qtrue; + } + else if ( !strcmp( argv[ i ], "-keeplights" ) ) { + keepLights = qtrue; + Sys_Printf( "Leaving light entities on map after compile\n" ); + } + else if ( !strcmp( argv[ i ], "-nodetail" ) ) { + Sys_Printf( "Ignoring detail brushes\n" ) ; + nodetail = qtrue; + } + else if ( !strcmp( argv[ i ], "-fulldetail" ) ) { + Sys_Printf( "Turning detail brushes into structural brushes\n" ); + fulldetail = qtrue; + } + else if ( !strcmp( argv[ i ], "-nofog" ) ) { + Sys_Printf( "Fog volumes disabled\n" ); + nofog = qtrue; + } + else if ( !strcmp( argv[ i ], "-nosubdivide" ) ) { + Sys_Printf( "Disabling brush face subdivision\n" ); + nosubdivide = qtrue; + } + else if ( !strcmp( argv[ i ], "-leaktest" ) ) { + Sys_Printf( "Leaktest enabled\n" ); + leaktest = qtrue; + } + else if ( !strcmp( argv[ i ], "-verboseentities" ) ) { + Sys_Printf( "Verbose entities enabled\n" ); + verboseEntities = qtrue; + } + else if ( !strcmp( argv[ i ], "-nocurves" ) ) { + Sys_Printf( "Ignoring curved surfaces (patches)\n" ); + noCurveBrushes = qtrue; + } + else if ( !strcmp( argv[ i ], "-notjunc" ) ) { + Sys_Printf( "T-junction fixing disabled\n" ); + notjunc = qtrue; + } + else if ( !strcmp( argv[ i ], "-fakemap" ) ) { + Sys_Printf( "Generating fakemap.map\n" ); + fakemap = qtrue; + } + else if ( !strcmp( argv[ i ], "-samplesize" ) ) { + sampleSize = atoi( argv[ i + 1 ] ); + if ( sampleSize < 1 ) { + sampleSize = 1; + } + i++; + Sys_Printf( "Lightmap sample size set to %dx%d units\n", sampleSize, sampleSize ); + } + else if ( !strcmp( argv[ i ], "-minsamplesize" ) ) { + minSampleSize = atoi( argv[ i + 1 ] ); + if ( minSampleSize < 1 ) { + minSampleSize = 1; + } + i++; + Sys_Printf( "Minimum lightmap sample size set to %dx%d units\n", minSampleSize, minSampleSize ); + } + else if ( !strcmp( argv[ i ], "-custinfoparms" ) ) { + Sys_Printf( "Custom info parms enabled\n" ); + useCustomInfoParms = qtrue; + } + + /* sof2 args */ + else if ( !strcmp( argv[ i ], "-rename" ) ) { + Sys_Printf( "Appending _bsp suffix to misc_model shaders (SOF2)\n" ); + renameModelShaders = qtrue; + } + + /* ydnar args */ + else if ( !strcmp( argv[ i ], "-ne" ) ) { + normalEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Normal epsilon set to %f\n", normalEpsilon ); + } + else if ( !strcmp( argv[ i ], "-de" ) ) { + distanceEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Distance epsilon set to %f\n", distanceEpsilon ); + } + else if ( !strcmp( argv[ i ], "-mv" ) ) { + maxLMSurfaceVerts = atoi( argv[ i + 1 ] ); + if ( maxLMSurfaceVerts < 3 ) { + maxLMSurfaceVerts = 3; + } + if ( maxLMSurfaceVerts > maxSurfaceVerts ) { + maxSurfaceVerts = maxLMSurfaceVerts; + } + i++; + Sys_Printf( "Maximum lightmapped surface vertex count set to %d\n", maxLMSurfaceVerts ); + } + else if ( !strcmp( argv[ i ], "-mi" ) ) { + maxSurfaceIndexes = atoi( argv[ i + 1 ] ); + if ( maxSurfaceIndexes < 3 ) { + maxSurfaceIndexes = 3; + } + i++; + Sys_Printf( "Maximum per-surface index count set to %d\n", maxSurfaceIndexes ); + } + else if ( !strcmp( argv[ i ], "-np" ) ) { + npDegrees = atof( argv[ i + 1 ] ); + if ( npDegrees < 0.0f ) { + npDegrees = 0.0f; + } + else if ( npDegrees > 0.0f ) { + Sys_Printf( "Forcing nonplanar surfaces with a breaking angle of %f degrees\n", npDegrees ); + } + i++; + } + else if ( !strcmp( argv[ i ], "-snap" ) ) { + bevelSnap = atoi( argv[ i + 1 ] ); + if ( bevelSnap < 0 ) { + bevelSnap = 0; + } + i++; + if ( bevelSnap > 0 ) { + Sys_Printf( "Snapping brush bevel planes to %d units\n", bevelSnap ); + } + } + else if ( !strcmp( argv[ i ], "-texrange" ) ) { + texRange = atoi( argv[ i + 1 ] ); + if ( texRange < 0 ) { + texRange = 0; + } + i++; + Sys_Printf( "Limiting per-surface texture range to %d texels\n", texRange ); + } + else if ( !strcmp( argv[ i ], "-nohint" ) ) { + Sys_Printf( "Hint brushes disabled\n" ); + noHint = qtrue; + } + else if ( !strcmp( argv[ i ], "-flat" ) ) { + Sys_Printf( "Flatshading enabled\n" ); + flat = qtrue; + } + else if ( !strcmp( argv[ i ], "-celshader" ) ) { + ++i; + if ( argv[i][0] ) { + sprintf( globalCelShader, "textures/%s", argv[ i ] ); + } + else{ + *globalCelShader = 0; + } + Sys_Printf( "Global cel shader set to \"%s\"\n", globalCelShader ); + } + else if ( !strcmp( argv[ i ], "-meta" ) ) { + Sys_Printf( "Creating meta surfaces from brush faces\n" ); + meta = qtrue; + } + else if ( !strcmp( argv[ i ], "-metaadequatescore" ) ) { + metaAdequateScore = atoi( argv[ i + 1 ] ); + if ( metaAdequateScore < 0 ) { + metaAdequateScore = -1; + } + i++; + if ( metaAdequateScore >= 0 ) { + Sys_Printf( "Setting ADEQUATE meta score to %d (see surface_meta.c)\n", metaAdequateScore ); + } + } + else if ( !strcmp( argv[ i ], "-metagoodscore" ) ) { + metaGoodScore = atoi( argv[ i + 1 ] ); + if ( metaGoodScore < 0 ) { + metaGoodScore = -1; + } + i++; + if ( metaGoodScore >= 0 ) { + Sys_Printf( "Setting GOOD meta score to %d (see surface_meta.c)\n", metaGoodScore ); + } + } + else if ( !strcmp( argv[ i ], "-metamaxbboxdistance" ) ) { + metaMaxBBoxDistance = atof( argv[ i + 1 ] ); + if ( metaMaxBBoxDistance < 0 ) { + metaMaxBBoxDistance = -1; + } + i++; + if ( metaMaxBBoxDistance >= 0 ) { + Sys_Printf( "Setting meta maximum bounding box distance to %f\n", metaMaxBBoxDistance ); + } + } + else if ( !strcmp( argv[ i ], "-patchmeta" ) ) { + Sys_Printf( "Creating meta surfaces from patches\n" ); + patchMeta = qtrue; + } + else if ( !strcmp( argv[ i ], "-flares" ) ) { + Sys_Printf( "Flare surfaces enabled\n" ); + emitFlares = qtrue; + } + else if ( !strcmp( argv[ i ], "-noflares" ) ) { + Sys_Printf( "Flare surfaces disabled\n" ); + emitFlares = qfalse; + } + else if ( !strcmp( argv[ i ], "-skyfix" ) ) { + Sys_Printf( "GL_CLAMP sky fix/hack/workaround enabled\n" ); + skyFixHack = qtrue; + } + else if ( !strcmp( argv[ i ], "-debugsurfaces" ) ) { + Sys_Printf( "emitting debug surfaces\n" ); + debugSurfaces = qtrue; + } + else if ( !strcmp( argv[ i ], "-debuginset" ) ) { + Sys_Printf( "Debug surface triangle insetting enabled\n" ); + debugInset = qtrue; + } + else if ( !strcmp( argv[ i ], "-debugportals" ) ) { + Sys_Printf( "Debug portal surfaces enabled\n" ); + debugPortals = qtrue; + } + else if ( !strcmp( argv[ i ], "-sRGBtex" ) ) { + texturesRGB = qtrue; + Sys_Printf( "Textures are in sRGB\n" ); + } + else if ( !strcmp( argv[ i ], "-nosRGBtex" ) ) { + texturesRGB = qfalse; + Sys_Printf( "Textures are linear\n" ); + } + else if ( !strcmp( argv[ i ], "-sRGBcolor" ) ) { + colorsRGB = qtrue; + Sys_Printf( "Colors are in sRGB\n" ); + } + else if ( !strcmp( argv[ i ], "-nosRGBcolor" ) ) { + colorsRGB = qfalse; + Sys_Printf( "Colors are linear\n" ); + } + else if ( !strcmp( argv[ i ], "-nosRGB" ) ) { + texturesRGB = qfalse; + Sys_Printf( "Textures are linear\n" ); + colorsRGB = qfalse; + Sys_Printf( "Colors are linear\n" ); + } + else if ( !strcmp( argv[ i ], "-altsplit" ) ) + { + Sys_Printf( "Alternate BSP splitting (by 27) enabled\n" ); + bspAlternateSplitWeights = qtrue; + } + else if ( !strcmp( argv[ i ], "-deep" ) ) + { + Sys_Printf( "Deep BSP tree generation enabled\n" ); + deepBSP = qtrue; + } + else if ( !strcmp( argv[ i ], "-maxarea" ) ) { + Sys_Printf( "Max Area face surface generation enabled\n" ); + maxAreaFaceSurface = qtrue; + } + else if ( !strcmp( argv[ i ], "-bspfile" ) ) + { + strcpy( BSPFilePath, argv[i + 1] ); + i++; + Sys_Printf( "Use %s as bsp file\n", BSPFilePath ); + } + else if ( !strcmp( argv[ i ], "-linfile" ) ) + { + strcpy( lineFilePath, argv[i + 1] ); + i++; + Sys_Printf( "Use %s as line file\n", lineFilePath ); + } + else if ( !strcmp( argv[ i ], "-prtfile" ) ) + { + strcpy( portalFilePath, argv[i + 1] ); + i++; + Sys_Printf( "Use %s as portal file\n", portalFilePath ); + } + else if ( !strcmp( argv[ i ], "-srffile" ) ) + { + strcpy( surfaceFilePath, argv[i + 1] ); + i++; + Sys_Printf( "Use %s as surface file\n", surfaceFilePath ); + } + else if ( !strcmp( argv[ i ], "-bsp" ) ) { + Sys_Printf( "-bsp argument unnecessary\n" ); + } + else{ + Sys_FPrintf( SYS_WRN, "WARNING: Unknown option \"%s\"\n", argv[ i ] ); + } + } + + /* fixme: print more useful usage here */ + if ( i != ( argc - 1 ) ) { + Error( "usage: q3map [options] mapfile" ); + } + + /* copy source name */ + strcpy( source, ExpandArg( argv[ i ] ) ); + StripExtension( source ); + + /* ydnar: set default sample size */ + SetDefaultSampleSize( sampleSize ); + + if (!BSPFilePath[0]) { + sprintf( BSPFilePath, "%s.bsp", source ); + } + if (!lineFilePath[0]) { + sprintf( lineFilePath, "%s.lin", source ); + } + if (!portalFilePath[0]) { + sprintf( portalFilePath, "%s.prt", source ); + } + if (!surfaceFilePath[0]) { + sprintf( surfaceFilePath, "%s.srf", source ); + } + + /* delete portal, line and surface files */ + remove( portalFilePath ); + remove( lineFilePath ); + //% remove( surfaceFilePath ) /* ydnar */ + + /* expand mapname */ + strcpy( name, ExpandArg( argv[ i ] ) ); + if ( strcmp( name + strlen( name ) - 4, ".reg" ) ) { + /* if we are doing a full map, delete the last saved region map */ + sprintf( path, "%s.reg", source ); + remove( path ); + DefaultExtension( name, ".map" ); /* might be .reg */ + } + + /* if onlyents, just grab the entites and resave */ + if ( onlyents ) { + OnlyEnts( BSPFilePath ); + return 0; + } + + /* load shaders */ + LoadShaderInfo(); + + /* load original file from temp spot in case it was renamed by the editor on the way in */ + if ( strlen( tempSource ) > 0 ) { + LoadMapFile( tempSource, qfalse, qfalse ); + } + else{ + LoadMapFile( name, qfalse, qfalse ); + } + + /* div0: inject command line parameters */ + InjectCommandLine( argv, 1, argc - 1 ); + + /* ydnar: decal setup */ + ProcessDecals(); + + /* ydnar: cloned brush model entities */ + SetCloneModelNumbers(); + + /* process world and submodels */ + ProcessModels( portalFilePath, lineFilePath ); + + /* set light styles from targetted light entities */ + SetLightStyles(); + + /* finish and write bsp */ + EndBSPFile( qtrue, BSPFilePath, surfaceFilePath ); + + /* remove temp map source file if appropriate */ + if ( strlen( tempSource ) > 0 ) { + remove( tempSource ); + } + + /* return to sender */ + return 0; +} diff --git a/tools/vmap/bsp_analyze.c b/tools/vmap/bsp_analyze.c new file mode 100644 index 0000000..3a75f99 --- /dev/null +++ b/tools/vmap/bsp_analyze.c @@ -0,0 +1,195 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* dependencies */ +#include "vmap.h" + + + +/* + AnalyzeBSPMain() - ydnar + analyzes a Quake engine BSP file + */ + +typedef struct abspHeader_s +{ + char ident[ 4 ]; + int version; + + bspLump_t lumps[ 1 ]; /* unknown size */ +} +abspHeader_t; + +typedef struct abspLumpTest_s +{ + int radix, minCount; + char *name; +} +abspLumpTest_t; + +int AnalyzeBSPMain( int argc, char **argv ){ + abspHeader_t *header; + int size, i, version, offset, length, lumpInt, count; + char ident[ 5 ]; + void *lump; + float lumpFloat; + char lumpString[ 1024 ], source[ 1024 ]; + qboolean lumpSwap = qfalse; + abspLumpTest_t *lumpTest; + static abspLumpTest_t lumpTests[] = + { + { sizeof( bspPlane_t ), 6, "IBSP LUMP_PLANES" }, + { sizeof( bspBrush_t ), 1, "IBSP LUMP_BRUSHES" }, + { 8, 6, "IBSP LUMP_BRUSHSIDES" }, + { sizeof( bspBrushSide_t ), 6, "RBSP LUMP_BRUSHSIDES" }, + { sizeof( bspModel_t ), 1, "IBSP LUMP_MODELS" }, + { sizeof( bspNode_t ), 2, "IBSP LUMP_NODES" }, + { sizeof( bspLeaf_t ), 1, "IBSP LUMP_LEAFS" }, + { 104, 3, "IBSP LUMP_DRAWSURFS" }, + { 44, 3, "IBSP LUMP_DRAWVERTS" }, + { 4, 6, "IBSP LUMP_DRAWINDEXES" }, + { 128 * 128 * 3, 1, "IBSP LUMP_LIGHTMAPS" }, + { 256 * 256 * 3, 1, "IBSP LUMP_LIGHTMAPS (256 x 256)" }, + { 512 * 512 * 3, 1, "IBSP LUMP_LIGHTMAPS (512 x 512)" }, + { 0, 0, NULL } + }; + + + /* arg checking */ + if ( argc < 1 ) { + Sys_Printf( "Usage: q3map -analyze [-lumpswap] [-v] \n" ); + return 0; + } + + /* process arguments */ + for ( i = 1; i < ( argc - 1 ); i++ ) + { + /* -format map|ase|... */ + if ( !strcmp( argv[ i ], "-lumpswap" ) ) { + Sys_Printf( "Swapped lump structs enabled\n" ); + lumpSwap = qtrue; + } + } + + /* clean up map name */ + strcpy( source, ExpandArg( argv[ i ] ) ); + Sys_Printf( "Loading %s\n", source ); + + /* load the file */ + size = LoadFile( source, (void**) &header ); + if ( size == 0 || header == NULL ) { + Sys_Printf( "Unable to load %s.\n", source ); + return -1; + } + + /* analyze ident/version */ + memcpy( ident, header->ident, 4 ); + ident[ 4 ] = '\0'; + version = LittleLong( header->version ); + + Sys_Printf( "Identity: %s\n", ident ); + Sys_Printf( "Version: %d\n", version ); + Sys_Printf( "---------------------------------------\n" ); + + /* analyze each lump */ + for ( i = 0; i < 100; i++ ) + { + /* call of duty swapped lump pairs */ + if ( lumpSwap ) { + offset = LittleLong( header->lumps[ i ].length ); + length = LittleLong( header->lumps[ i ].offset ); + } + + /* standard lump pairs */ + else + { + offset = LittleLong( header->lumps[ i ].offset ); + length = LittleLong( header->lumps[ i ].length ); + } + + /* extract data */ + lump = (byte*) header + offset; + lumpInt = LittleLong( (int) *( (int*) lump ) ); + lumpFloat = LittleFloat( (float) *( (float*) lump ) ); + memcpy( lumpString, (char*) lump, ( (size_t)length < sizeof( lumpString ) ? (size_t)length : sizeof( lumpString ) - 1 ) ); + lumpString[ sizeof( lumpString ) - 1 ] = '\0'; + + /* print basic lump info */ + Sys_Printf( "Lump: %d\n", i ); + Sys_Printf( "Offset: %d bytes\n", offset ); + Sys_Printf( "Length: %d bytes\n", length ); + + /* only operate on valid lumps */ + if ( length > 0 ) { + /* print data in 4 formats */ + Sys_Printf( "As hex: %08X\n", lumpInt ); + Sys_Printf( "As int: %d\n", lumpInt ); + Sys_Printf( "As float: %f\n", lumpFloat ); + Sys_Printf( "As string: %s\n", lumpString ); + + /* guess lump type */ + if ( lumpString[ 0 ] == '{' && lumpString[ 2 ] == '"' ) { + Sys_Printf( "Type guess: IBSP LUMP_ENTITIES\n" ); + } + else if ( strstr( lumpString, "textures/" ) ) { + Sys_Printf( "Type guess: IBSP LUMP_SHADERS\n" ); + } + else + { + /* guess based on size/count */ + for ( lumpTest = lumpTests; lumpTest->radix > 0; lumpTest++ ) + { + if ( ( length % lumpTest->radix ) != 0 ) { + continue; + } + count = length / lumpTest->radix; + if ( count < lumpTest->minCount ) { + continue; + } + Sys_Printf( "Type guess: %s (%d x %d)\n", lumpTest->name, count, lumpTest->radix ); + } + } + } + + Sys_Printf( "---------------------------------------\n" ); + + /* end of file */ + if ( offset + length >= size ) { + break; + } + } + + /* last stats */ + Sys_Printf( "Lump count: %d\n", i + 1 ); + Sys_Printf( "File size: %d bytes\n", size ); + + /* return to caller */ + return 0; +} diff --git a/tools/vmap/bsp_info.c b/tools/vmap/bsp_info.c new file mode 100644 index 0000000..814a255 --- /dev/null +++ b/tools/vmap/bsp_info.c @@ -0,0 +1,94 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* dependencies */ +#include "vmap.h" + + + +/* + BSPInfoMain() + emits statistics about the bsp file + */ + +int BSPInfoMain( int count, char **fileNames ){ + int i; + char source[ 1024 ], ext[ 64 ]; + int size; + FILE *f; + + + /* dummy check */ + if ( count < 1 ) { + Sys_Printf( "No files to dump info for.\n" ); + return -1; + } + + /* enable info mode */ + infoMode = qtrue; + + /* walk file list */ + for ( i = 0; i < count; i++ ) + { + Sys_Printf( "---------------------------------\n" ); + + /* mangle filename and get size */ + strcpy( source, fileNames[ i ] ); + ExtractFileExtension( source, ext ); + if ( !Q_stricmp( ext, "map" ) ) { + StripExtension( source ); + } + DefaultExtension( source, ".bsp" ); + f = fopen( source, "rb" ); + if ( f ) { + size = Q_filelength( f ); + fclose( f ); + } + else{ + size = 0; + } + + /* load the bsp file and print lump sizes */ + Sys_Printf( "%s\n", source ); + LoadBSPFile( source ); + PrintBSPFileSizes(); + + /* print sizes */ + Sys_Printf( "\n" ); + Sys_Printf( " total %9d\n", size ); + Sys_Printf( " %9d KB\n", size / 1024 ); + Sys_Printf( " %9d MB\n", size / ( 1024 * 1024 ) ); + + Sys_Printf( "---------------------------------\n" ); + } + + /* return count */ + return i; +} diff --git a/tools/vmap/bsp_scale.c b/tools/vmap/bsp_scale.c new file mode 100644 index 0000000..b285d83 --- /dev/null +++ b/tools/vmap/bsp_scale.c @@ -0,0 +1,355 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* dependencies */ +#include "vmap.h" + + + +static void ExtrapolateTexcoords( const float *axyz, const float *ast, const float *bxyz, const float *bst, const float *cxyz, const float *cst, const float *axyz_new, float *ast_out, const float *bxyz_new, float *bst_out, const float *cxyz_new, float *cst_out ){ + vec4_t scoeffs, tcoeffs; + float md; + m4x4_t solvematrix; + + vec3_t norm; + vec3_t dab, dac; + VectorSubtract( bxyz, axyz, dab ); + VectorSubtract( cxyz, axyz, dac ); + CrossProduct( dab, dac, norm ); + + // assume: + // s = f(x, y, z) + // s(v + norm) = s(v) when n ortho xyz + + // s(v) = DotProduct(v, scoeffs) + scoeffs[3] + + // solve: + // scoeffs * (axyz, 1) == ast[0] + // scoeffs * (bxyz, 1) == bst[0] + // scoeffs * (cxyz, 1) == cst[0] + // scoeffs * (norm, 0) == 0 + // scoeffs * [axyz, 1 | bxyz, 1 | cxyz, 1 | norm, 0] = [ast[0], bst[0], cst[0], 0] + solvematrix[0] = axyz[0]; + solvematrix[4] = axyz[1]; + solvematrix[8] = axyz[2]; + solvematrix[12] = 1; + solvematrix[1] = bxyz[0]; + solvematrix[5] = bxyz[1]; + solvematrix[9] = bxyz[2]; + solvematrix[13] = 1; + solvematrix[2] = cxyz[0]; + solvematrix[6] = cxyz[1]; + solvematrix[10] = cxyz[2]; + solvematrix[14] = 1; + solvematrix[3] = norm[0]; + solvematrix[7] = norm[1]; + solvematrix[11] = norm[2]; + solvematrix[15] = 0; + + md = m4_det( solvematrix ); + if ( md * md < 1e-10 ) { + Sys_Printf( "Cannot invert some matrix, some texcoords aren't extrapolated!" ); + return; + } + + m4x4_invert( solvematrix ); + + scoeffs[0] = ast[0]; + scoeffs[1] = bst[0]; + scoeffs[2] = cst[0]; + scoeffs[3] = 0; + m4x4_transform_vec4( solvematrix, scoeffs ); + tcoeffs[0] = ast[1]; + tcoeffs[1] = bst[1]; + tcoeffs[2] = cst[1]; + tcoeffs[3] = 0; + m4x4_transform_vec4( solvematrix, tcoeffs ); + + ast_out[0] = scoeffs[0] * axyz_new[0] + scoeffs[1] * axyz_new[1] + scoeffs[2] * axyz_new[2] + scoeffs[3]; + ast_out[1] = tcoeffs[0] * axyz_new[0] + tcoeffs[1] * axyz_new[1] + tcoeffs[2] * axyz_new[2] + tcoeffs[3]; + bst_out[0] = scoeffs[0] * bxyz_new[0] + scoeffs[1] * bxyz_new[1] + scoeffs[2] * bxyz_new[2] + scoeffs[3]; + bst_out[1] = tcoeffs[0] * bxyz_new[0] + tcoeffs[1] * bxyz_new[1] + tcoeffs[2] * bxyz_new[2] + tcoeffs[3]; + cst_out[0] = scoeffs[0] * cxyz_new[0] + scoeffs[1] * cxyz_new[1] + scoeffs[2] * cxyz_new[2] + scoeffs[3]; + cst_out[1] = tcoeffs[0] * cxyz_new[0] + tcoeffs[1] * cxyz_new[1] + tcoeffs[2] * cxyz_new[2] + tcoeffs[3]; +} + +/* + ScaleBSPMain() + amaze and confuse your enemies with wierd scaled maps! + */ + +int ScaleBSPMain( int argc, char **argv ){ + int i, j; + float f, a; + vec3_t scale; + vec3_t vec; + char str[ 1024 ]; + int uniform, axis; + qboolean texscale; + float *old_xyzst = NULL; + float spawn_ref = 0; + + + /* arg checking */ + if ( argc < 3 ) { + Sys_Printf( "Usage: q3map [-v] -scale [-tex] [-spawn_ref ] \n" ); + return 0; + } + + texscale = qfalse; + for ( i = 1; i < argc - 2; ++i ) + { + if ( !strcmp( argv[i], "-tex" ) ) { + texscale = qtrue; + } + else if ( !strcmp( argv[i], "-spawn_ref" ) ) { + spawn_ref = atof( argv[i + 1] ); + ++i; + } + else{ + break; + } + } + + /* get scale */ + // if(argc-2 >= i) // always true + scale[2] = scale[1] = scale[0] = atof( argv[ argc - 2 ] ); + if ( argc - 3 >= i ) { + scale[1] = scale[0] = atof( argv[ argc - 3 ] ); + } + if ( argc - 4 >= i ) { + scale[0] = atof( argv[ argc - 4 ] ); + } + + uniform = ( ( scale[0] == scale[1] ) && ( scale[1] == scale[2] ) ); + + if ( scale[0] == 0.0f || scale[1] == 0.0f || scale[2] == 0.0f ) { + Sys_Printf( "Usage: q3map [-v] -scale [-tex] [-spawn_ref ] \n" ); + Sys_Printf( "Non-zero scale value required.\n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + ParseEntities(); + + /* note it */ + Sys_Printf( "--- ScaleBSP ---\n" ); + Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities ); + + /* scale entity keys */ + for ( i = 0; i < numBSPEntities && i < numEntities; i++ ) + { + /* scale origin */ + GetVectorForKey( &entities[ i ], "origin", vec ); + if ( ( vec[ 0 ] || vec[ 1 ] || vec[ 2 ] ) ) { + if ( !strncmp( ValueForKey( &entities[i], "classname" ), "info_player_", 12 ) ) { + vec[2] += spawn_ref; + } + vec[0] *= scale[0]; + vec[1] *= scale[1]; + vec[2] *= scale[2]; + if ( !strncmp( ValueForKey( &entities[i], "classname" ), "info_player_", 12 ) ) { + vec[2] -= spawn_ref; + } + sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] ); + SetKeyValue( &entities[ i ], "origin", str ); + } + + a = FloatForKey( &entities[ i ], "angle" ); + if ( a == -1 || a == -2 ) { // z scale + axis = 2; + } + else if ( fabs( sin( DEG2RAD( a ) ) ) < 0.707 ) { + axis = 0; + } + else{ + axis = 1; + } + + /* scale door lip */ + f = FloatForKey( &entities[ i ], "lip" ); + if ( f ) { + f *= scale[axis]; + sprintf( str, "%f", f ); + SetKeyValue( &entities[ i ], "lip", str ); + } + + /* scale plat height */ + f = FloatForKey( &entities[ i ], "height" ); + if ( f ) { + f *= scale[2]; + sprintf( str, "%f", f ); + SetKeyValue( &entities[ i ], "height", str ); + } + + // TODO maybe allow a definition file for entities to specify which values are scaled how? + } + + /* scale models */ + for ( i = 0; i < numBSPModels; i++ ) + { + bspModels[ i ].mins[0] *= scale[0]; + bspModels[ i ].mins[1] *= scale[1]; + bspModels[ i ].mins[2] *= scale[2]; + bspModels[ i ].maxs[0] *= scale[0]; + bspModels[ i ].maxs[1] *= scale[1]; + bspModels[ i ].maxs[2] *= scale[2]; + } + + /* scale nodes */ + for ( i = 0; i < numBSPNodes; i++ ) + { + bspNodes[ i ].mins[0] *= scale[0]; + bspNodes[ i ].mins[1] *= scale[1]; + bspNodes[ i ].mins[2] *= scale[2]; + bspNodes[ i ].maxs[0] *= scale[0]; + bspNodes[ i ].maxs[1] *= scale[1]; + bspNodes[ i ].maxs[2] *= scale[2]; + } + + /* scale leafs */ + for ( i = 0; i < numBSPLeafs; i++ ) + { + bspLeafs[ i ].mins[0] *= scale[0]; + bspLeafs[ i ].mins[1] *= scale[1]; + bspLeafs[ i ].mins[2] *= scale[2]; + bspLeafs[ i ].maxs[0] *= scale[0]; + bspLeafs[ i ].maxs[1] *= scale[1]; + bspLeafs[ i ].maxs[2] *= scale[2]; + } + + if ( texscale ) { + Sys_Printf( "Using texture unlocking (and probably breaking texture alignment a lot)\n" ); + old_xyzst = safe_malloc( sizeof( *old_xyzst ) * numBSPDrawVerts * 5 ); + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + old_xyzst[5 * i + 0] = bspDrawVerts[i].xyz[0]; + old_xyzst[5 * i + 1] = bspDrawVerts[i].xyz[1]; + old_xyzst[5 * i + 2] = bspDrawVerts[i].xyz[2]; + old_xyzst[5 * i + 3] = bspDrawVerts[i].st[0]; + old_xyzst[5 * i + 4] = bspDrawVerts[i].st[1]; + } + } + + /* scale drawverts */ + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + bspDrawVerts[i].xyz[0] *= scale[0]; + bspDrawVerts[i].xyz[1] *= scale[1]; + bspDrawVerts[i].xyz[2] *= scale[2]; + bspDrawVerts[i].normal[0] /= scale[0]; + bspDrawVerts[i].normal[1] /= scale[1]; + bspDrawVerts[i].normal[2] /= scale[2]; + VectorNormalize( bspDrawVerts[i].normal, bspDrawVerts[i].normal ); + } + + if ( texscale ) { + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + switch ( bspDrawSurfaces[i].surfaceType ) + { + case SURFACE_FACE: + case SURFACE_META: + if ( bspDrawSurfaces[i].numIndexes % 3 ) { + Error( "Not a triangulation!" ); + } + for ( j = bspDrawSurfaces[i].firstIndex; j < bspDrawSurfaces[i].firstIndex + bspDrawSurfaces[i].numIndexes; j += 3 ) + { + int ia = bspDrawIndexes[j] + bspDrawSurfaces[i].firstVert, ib = bspDrawIndexes[j + 1] + bspDrawSurfaces[i].firstVert, ic = bspDrawIndexes[j + 2] + bspDrawSurfaces[i].firstVert; + bspDrawVert_t *a = &bspDrawVerts[ia], *b = &bspDrawVerts[ib], *c = &bspDrawVerts[ic]; + float *oa = &old_xyzst[ia * 5], *ob = &old_xyzst[ib * 5], *oc = &old_xyzst[ic * 5]; + // extrapolate: + // a->xyz -> oa + // b->xyz -> ob + // c->xyz -> oc + ExtrapolateTexcoords( + &oa[0], &oa[3], + &ob[0], &ob[3], + &oc[0], &oc[3], + a->xyz, a->st, + b->xyz, b->st, + c->xyz, c->st ); + } + break; + } + } + } + + /* scale planes */ + if ( uniform ) { + for ( i = 0; i < numBSPPlanes; i++ ) + { + bspPlanes[ i ].dist *= scale[0]; + } + } + else + { + for ( i = 0; i < numBSPPlanes; i++ ) + { + bspPlanes[ i ].normal[0] /= scale[0]; + bspPlanes[ i ].normal[1] /= scale[1]; + bspPlanes[ i ].normal[2] /= scale[2]; + f = 1 / VectorLength( bspPlanes[i].normal ); + VectorScale( bspPlanes[i].normal, f, bspPlanes[i].normal ); + bspPlanes[ i ].dist *= f; + } + } + + /* scale gridsize */ + GetVectorForKey( &entities[ 0 ], "gridsize", vec ); + if ( ( vec[ 0 ] + vec[ 1 ] + vec[ 2 ] ) == 0.0f ) { + VectorCopy( gridSize, vec ); + } + vec[0] *= scale[0]; + vec[1] *= scale[1]; + vec[2] *= scale[2]; + sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] ); + SetKeyValue( &entities[ 0 ], "gridsize", str ); + + /* inject command line parameters */ + InjectCommandLine( argv, 0, argc - 1 ); + + /* write the bsp */ + UnparseEntities(); + StripExtension( source ); + DefaultExtension( source, "_s.bsp" ); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + /* return to sender */ + return 0; +} diff --git a/tools/vmap/bspfile_abstract.c b/tools/vmap/bspfile_abstract.c new file mode 100644 index 0000000..dfe59dd --- /dev/null +++ b/tools/vmap/bspfile_abstract.c @@ -0,0 +1,1114 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSPFILE_ABSTRACT_C + + + +/* dependencies */ +#include "vmap.h" + + + + +/* ------------------------------------------------------------------------------- + + this file was copied out of the common directory in order to not break + compatibility with the q3map 1.x tree. it was moved out in order to support + the raven bsp format (RBSP) used in soldier of fortune 2 and jedi knight 2. + + since each game has its own set of particular features, the data structures + below no longer directly correspond to the binary format of a particular game. + + the translation will be done at bsp load/save time to keep any sort of + special-case code messiness out of the rest of the program. + + ------------------------------------------------------------------------------- */ + + + +/* FIXME: remove the functions below that handle memory management of bsp file chunks */ + +int numBSPDrawVertsBuffer = 0; +void IncDrawVerts(){ + numBSPDrawVerts++; + + if ( bspDrawVerts == 0 ) { + numBSPDrawVertsBuffer = 1024; + + bspDrawVerts = safe_malloc_info( sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer, "IncDrawVerts" ); + + } + else if ( numBSPDrawVerts > numBSPDrawVertsBuffer ) { + numBSPDrawVertsBuffer *= 3; // multiply by 1.5 + numBSPDrawVertsBuffer /= 2; + + bspDrawVerts = realloc( bspDrawVerts, sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer ); + + if ( !bspDrawVerts ) { + Error( "realloc() failed (IncDrawVerts)" ); + } + } + + memset( bspDrawVerts + ( numBSPDrawVerts - 1 ), 0, sizeof( bspDrawVert_t ) ); +} + +void SetDrawVerts( int n ){ + if ( bspDrawVerts != 0 ) { + free( bspDrawVerts ); + } + + numBSPDrawVerts = n; + numBSPDrawVertsBuffer = numBSPDrawVerts; + + bspDrawVerts = safe_malloc_info( sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer, "IncDrawVerts" ); + + memset( bspDrawVerts, 0, n * sizeof( bspDrawVert_t ) ); +} + +int numBSPDrawSurfacesBuffer = 0; +void SetDrawSurfacesBuffer(){ + if ( bspDrawSurfaces != 0 ) { + free( bspDrawSurfaces ); + } + + numBSPDrawSurfacesBuffer = MAX_MAP_DRAW_SURFS; + + bspDrawSurfaces = safe_malloc_info( sizeof( bspDrawSurface_t ) * numBSPDrawSurfacesBuffer, "IncDrawSurfaces" ); + bspDrawSurfaceCubemaps = safe_malloc_info( sizeof( *bspDrawSurfaceCubemaps ) * numBSPDrawSurfacesBuffer, "IncDrawSurfaceCubemaps" ); + + memset( bspDrawSurfaces, 0, MAX_MAP_DRAW_SURFS * sizeof( bspDrawSurface_t ) ); + memset( bspDrawSurfaceCubemaps, 0xff, MAX_MAP_DRAW_SURFS * sizeof( *bspDrawSurfaceCubemaps ) ); +} + +void SetDrawSurfaces( int n ){ + if ( bspDrawSurfaces != 0 ) { + free( bspDrawSurfaces ); + } + + numBSPDrawSurfaces = n; + numBSPDrawSurfacesBuffer = numBSPDrawSurfaces; + + bspDrawSurfaces = safe_malloc_info( sizeof( bspDrawSurface_t ) * numBSPDrawSurfacesBuffer, "IncDrawSurfaces" ); + bspDrawSurfaceCubemaps = safe_malloc_info( sizeof( *bspDrawSurfaceCubemaps ) * numBSPDrawSurfacesBuffer, "IncDrawSurfaceCubemaps" ); + + memset( bspDrawSurfaces, 0, n * sizeof( bspDrawSurface_t ) ); + memset( bspDrawSurfaceCubemaps, 0xff, n * sizeof( *bspDrawSurfaceCubemaps ) ); +} + +void BSPFilesCleanup(){ + if ( bspDrawVerts != 0 ) { + free( bspDrawVerts ); + } + if ( bspDrawSurfaces != 0 ) { + free( bspDrawSurfaces ); + } + if ( bspLightBytes != 0 ) { + free( bspLightBytes ); + } + if ( bspGridPoints != 0 ) { + free( bspGridPoints ); + } +} + + + + +typedef struct { + char lumpname[24]; // up to 23 chars, zero-padded + unsigned int fileofs; // from file start + unsigned int filelen; +} bspx_lump_t; +typedef struct bspx_header_s { + char id[4]; // 'BSPX' + unsigned int numlumps; + bspx_lump_t lumps[1]; +} bspx_header_t; +void *BSPX_FindLump(const char *lumpname, size_t *lumpsize) +{ + size_t i; + *lumpsize = 0; + if (!bspx) + return NULL; + + for (i = 0; i < bspx->numlumps; i++) + { + if (!strncmp(bspx->lumps[i].lumpname, lumpname, 24)) + { + *lumpsize = bspx->lumps[i].lumpsize; + return (char*)bspx->lumps[i].data; + } + } + return NULL; +} +void BSPX_ReadSurfExtensions(void) +{ + int *in; + size_t lumpsize; + + in = BSPX_FindLump("SURFENVMAP", &lumpsize); + if (in) + { + lumpsize /= sizeof(*in); + if (lumpsize == numBSPDrawSurfaces) + memcpy(bspDrawSurfaceCubemaps, in, lumpsize * sizeof(*bspDrawSurfaceCubemaps)); + } +} +void BSPX_Setup(void *filebase, size_t filelen, bspLump_t *lumps, size_t stdlumps) +{ + size_t i; + size_t offs = 0; + bspx_header_t *h; + + for (i = 0; i < stdlumps; i++, lumps++) + { + if (offs < (unsigned int)lumps->offset + (unsigned int)lumps->length) + offs = (unsigned int)lumps->offset + (unsigned int)lumps->length; + } + offs = (offs + 3) & ~3; + if (offs + sizeof(*h) > filelen) + return; /*no space for it*/ + h = (bspx_header_t*)((char*)filebase + offs); + + i = LittleLong(h->numlumps); + /*verify the header*/ + if (*(int*)h->id != (('B'<<0)|('S'<<8)|('P'<<16)|('X'<<24)) || + offs + sizeof(*h) + sizeof(h->lumps[0])*(i-1) > filelen) + return; + h->numlumps = i; + while(i-->0) + { + h->lumps[i].fileofs = LittleLong(h->lumps[i].fileofs); + h->lumps[i].filelen = LittleLong(h->lumps[i].filelen); + if ((unsigned int)h->lumps[i].fileofs + (unsigned int)h->lumps[i].filelen > filelen) + return; //truncated or something + } + + bspx = safe_malloc_info( sizeof(*bspx)-sizeof(*bspx->lumps) + h->numlumps*sizeof(*bspx->lumps), "bspx" ); + bspx->numlumps = h->numlumps; + for (i = 0; i < bspx->numlumps; i++) + { + bspx->lumps[i].lumpsize = h->lumps[i].filelen; + memcpy(bspx->lumps[i].lumpname, h->lumps[i].lumpname, sizeof(bspx->lumps[i].lumpname)); + bspx->lumps[i].lumpname[sizeof(bspx->lumps[i].lumpname)-1] = 0; + bspx->lumps[i].data = safe_malloc_info( (bspx->lumps[i].lumpsize+3)&~3u, bspx->lumps[i].lumpname ); + memcpy(bspx->lumps[i].data, (char*)filebase + h->lumps[i].fileofs, bspx->lumps[i].lumpsize); + } + + BSPX_ReadSurfExtensions(); +} +void BSPX_CopyOut(const char *lumpname, void *lumpdata, size_t lumpsize) +{ + size_t i, nl = bspx?bspx->numlumps:0; + bspx_t *n; + void *f; + + for (i = 0; i < nl; i++) + { + if (!strncmp(bspx->lumps[i].lumpname, lumpname, 24)) + break; + } + + if (!lumpsize) + { + if (bspx && i < nl) + { //remove the lump if it exists... + bspx->numlumps--; + memcpy(&bspx->lumps[i], &bspx->lumps[i+1], sizeof(*bspx->lumps)*(bspx->numlumps-i)); + } + return; + } + + if (i == nl) + { //expand the size, if needed + n = safe_malloc_info( sizeof(*bspx)-sizeof(bspx->lumps) + sizeof(*bspx->lumps)*(nl+1), "bspx" ); + memcpy(n->lumps, bspx->lumps, sizeof(*bspx->lumps)*nl); + strncpy(n->lumps[nl].lumpname, lumpname, sizeof(n->lumps[nl].lumpname)); + n->lumps[nl].data = NULL; + n->lumps[nl].lumpsize = 0; + nl++; + n->numlumps = nl; + free(bspx); + bspx = n; + } + + f = bspx->lumps[i].data; + bspx->lumps[i].data = safe_malloc_info( lumpsize+4, bspx->lumps[i].lumpname ); + memcpy(bspx->lumps[i].data, lumpdata, lumpsize); + memset((char*)bspx->lumps[i].data+lumpsize, 0, 4); //make sure any padding bytes are 0, to avoid weirdness and boost reproducibility + bspx->lumps[i].lumpsize = lumpsize; + free(f); +} +void BSPX_GenerateSurfExtensions(void) +{ + qboolean hasenvmaps = qfalse; + int i; + for (i = 0; i < numBSPDrawSurfaces; i++) + { + if (bspDrawSurfaceCubemaps[i] != -1) + hasenvmaps = qtrue; + } + BSPX_CopyOut("SURFENVMAP", bspDrawSurfaceCubemaps, hasenvmaps?numBSPDrawSurfaces * sizeof(*bspDrawSurfaceCubemaps):0); +} +void BSPX_WriteLumps(FILE *file, bspLump_t *lumps, size_t stdlumps) +{ + size_t offset = ftell(file); + unsigned int tmp; + size_t l; + + BSPX_GenerateSurfExtensions(); + + if (!bspx || !bspx->numlumps) + return; //nothing to write + + //write our bspx header + SafeWrite(file, "BSPX", 4); + tmp = LittleLong(bspx->numlumps); + SafeWrite(file, &tmp, sizeof(tmp)); + + offset += 8 + bspx->numlumps*sizeof(bspx_lump_t); + for (l = 0; l < bspx->numlumps; l++) + { + SafeWrite(file, bspx->lumps[l].lumpname, sizeof(bspx->lumps[l].lumpname)); + tmp = LittleLong(offset); + SafeWrite(file, &tmp, sizeof(tmp)); + offset = (offset+bspx->lumps[l].lumpsize+3)&~3u; //we're guessing here... + tmp = LittleLong(bspx->lumps[l].lumpsize); + SafeWrite(file, &tmp, sizeof(tmp)); + } + + //now write the lumps. hopefully we got the offsets right!... + for (l = 0; l < bspx->numlumps; l++) + SafeWrite(file, bspx->lumps[l].data, (bspx->lumps[l].lumpsize+3)&~3u); +} + + +/* + SwapBlock() + if all values are 32 bits, this can be used to swap everything + */ + +void SwapBlock( int *block, int size ){ + int i; + + + /* dummy check */ + if ( block == NULL ) { + return; + } + + /* swap */ + size >>= 2; + for ( i = 0; i < size; i++ ) + block[ i ] = LittleLong( block[ i ] ); +} + + + +/* + SwapBSPFile() + byte swaps all data in the abstract bsp + */ + +void SwapBSPFile( void ){ + int i, j; + + + /* models */ + SwapBlock( (int*) bspModels, numBSPModels * sizeof( bspModels[ 0 ] ) ); + + /* shaders (don't swap the name) */ + for ( i = 0; i < numBSPShaders ; i++ ) + { + bspShaders[ i ].contentFlags = LittleLong( bspShaders[ i ].contentFlags ); + bspShaders[ i ].surfaceFlags = LittleLong( bspShaders[ i ].surfaceFlags ); + } + + /* planes */ + SwapBlock( (int*) bspPlanes, numBSPPlanes * sizeof( bspPlanes[ 0 ] ) ); + + /* nodes */ + SwapBlock( (int*) bspNodes, numBSPNodes * sizeof( bspNodes[ 0 ] ) ); + + /* leafs */ + SwapBlock( (int*) bspLeafs, numBSPLeafs * sizeof( bspLeafs[ 0 ] ) ); + + /* leaffaces */ + SwapBlock( (int*) bspLeafSurfaces, numBSPLeafSurfaces * sizeof( bspLeafSurfaces[ 0 ] ) ); + + /* leafbrushes */ + SwapBlock( (int*) bspLeafBrushes, numBSPLeafBrushes * sizeof( bspLeafBrushes[ 0 ] ) ); + + // brushes + SwapBlock( (int*) bspBrushes, numBSPBrushes * sizeof( bspBrushes[ 0 ] ) ); + + // brushsides + SwapBlock( (int*) bspBrushSides, numBSPBrushSides * sizeof( bspBrushSides[ 0 ] ) ); + + // vis + ( (int*) &bspVisBytes )[ 0 ] = LittleLong( ( (int*) &bspVisBytes )[ 0 ] ); + ( (int*) &bspVisBytes )[ 1 ] = LittleLong( ( (int*) &bspVisBytes )[ 1 ] ); + + /* drawverts (don't swap colors) */ + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + bspDrawVerts[ i ].xyz[ 0 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 0 ] ); + bspDrawVerts[ i ].xyz[ 1 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 1 ] ); + bspDrawVerts[ i ].xyz[ 2 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 2 ] ); + bspDrawVerts[ i ].normal[ 0 ] = LittleFloat( bspDrawVerts[ i ].normal[ 0 ] ); + bspDrawVerts[ i ].normal[ 1 ] = LittleFloat( bspDrawVerts[ i ].normal[ 1 ] ); + bspDrawVerts[ i ].normal[ 2 ] = LittleFloat( bspDrawVerts[ i ].normal[ 2 ] ); + bspDrawVerts[ i ].st[ 0 ] = LittleFloat( bspDrawVerts[ i ].st[ 0 ] ); + bspDrawVerts[ i ].st[ 1 ] = LittleFloat( bspDrawVerts[ i ].st[ 1 ] ); + for ( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + bspDrawVerts[ i ].lightmap[ j ][ 0 ] = LittleFloat( bspDrawVerts[ i ].lightmap[ j ][ 0 ] ); + bspDrawVerts[ i ].lightmap[ j ][ 1 ] = LittleFloat( bspDrawVerts[ i ].lightmap[ j ][ 1 ] ); + } + } + + /* drawindexes */ + SwapBlock( (int*) bspDrawIndexes, numBSPDrawIndexes * sizeof( bspDrawIndexes[0] ) ); + + /* drawsurfs */ + /* note: rbsp files (and hence q3map2 abstract bsp) have byte lightstyles index arrays, this follows sof2map convention */ + SwapBlock( (int*) bspDrawSurfaces, numBSPDrawSurfaces * sizeof( bspDrawSurfaces[ 0 ] ) ); + + /* fogs */ + for ( i = 0; i < numBSPFogs; i++ ) + { + bspFogs[ i ].brushNum = LittleLong( bspFogs[ i ].brushNum ); + bspFogs[ i ].visibleSide = LittleLong( bspFogs[ i ].visibleSide ); + } +} + + + +/* + GetLumpElements() + gets the number of elements in a bsp lump + */ + +int GetLumpElements( bspHeader_t *header, int lump, int size ){ + /* check for odd size */ + if ( header->lumps[ lump ].length % size ) { + if ( force ) { + Sys_FPrintf( SYS_WRN, "WARNING: GetLumpElements: odd lump size (%d) in lump %d\n", header->lumps[ lump ].length, lump ); + return 0; + } + else{ + Error( "GetLumpElements: odd lump size (%d) in lump %d", header->lumps[ lump ].length, lump ); + } + } + + /* return element count */ + return header->lumps[ lump ].length / size; +} + + + +/* + GetLump() + returns a pointer to the specified lump + */ + +void *GetLump( bspHeader_t *header, int lump ){ + return (void*)( (byte*) header + header->lumps[ lump ].offset ); +} + + + +/* + CopyLump() + copies a bsp file lump into a destination buffer + */ + +int CopyLump( bspHeader_t *header, int lump, void *dest, int size ){ + int length, offset; + + + /* get lump length and offset */ + length = header->lumps[ lump ].length; + offset = header->lumps[ lump ].offset; + + /* handle erroneous cases */ + if ( length == 0 ) { + return 0; + } + if ( length % size ) { + if ( force ) { + Sys_FPrintf( SYS_WRN, "WARNING: CopyLump: odd lump size (%d) in lump %d\n", length, lump ); + return 0; + } + else{ + Error( "CopyLump: odd lump size (%d) in lump %d", length, lump ); + } + } + + /* copy block of memory and return */ + memcpy( dest, (byte*) header + offset, length ); + return length / size; +} + +int CopyLump_Allocate( bspHeader_t *header, int lump, void **dest, int size, int *allocationVariable ){ + /* get lump length and offset */ + *allocationVariable = header->lumps[ lump ].length / size; + *dest = realloc( *dest, size * *allocationVariable ); + return CopyLump( header, lump, *dest, size ); +} + + +/* + AddLump() + adds a lump to an outgoing bsp file + */ + +void AddLump( FILE *file, bspHeader_t *header, int lumpNum, const void *data, int length ){ + bspLump_t *lump; + + + /* add lump to bsp file header */ + lump = &header->lumps[ lumpNum ]; + lump->offset = LittleLong( ftell( file ) ); + lump->length = LittleLong( length ); + + /* write lump to file */ + SafeWrite( file, data, ( length + 3 ) & ~3 ); +} + + + +/* + LoadBSPFile() + loads a bsp file into memory + */ + +void LoadBSPFile( const char *filename ){ + /* dummy check */ + if ( game == NULL || game->load == NULL ) { + Error( "LoadBSPFile: unsupported BSP file format" ); + } + + /* load it, then byte swap the in-memory version */ + game->load( filename ); + SwapBSPFile(); +} + + + +/* + WriteBSPFile() + writes a bsp file + */ + +void WriteBSPFile( const char *filename ){ + char tempname[ 1024 ]; + time_t tm; + + + /* dummy check */ + if ( game == NULL || game->write == NULL ) { + Error( "WriteBSPFile: unsupported BSP file format" ); + } + + /* make fake temp name so existing bsp file isn't damaged in case write process fails */ + time( &tm ); + sprintf( tempname, "%s.%08X", filename, (int) tm ); + + /* byteswap, write the bsp, then swap back so it can be manipulated further */ + SwapBSPFile(); + game->write( tempname ); + SwapBSPFile(); + + /* replace existing bsp file */ + remove( filename ); + rename( tempname, filename ); +} + + + +/* + PrintBSPFileSizes() + dumps info about current file + */ + +void PrintBSPFileSizes( void ){ + /* parse entities first */ + if ( numEntities <= 0 ) { + ParseEntities(); + } + + /* note that this is abstracted */ + Sys_Printf( "Abstracted BSP file components (*actual sizes may differ)\n" ); + + /* print various and sundry bits */ + Sys_Printf( "%9d models %9d\n", + numBSPModels, (int) ( numBSPModels * sizeof( bspModel_t ) ) ); + Sys_Printf( "%9d shaders %9d\n", + numBSPShaders, (int) ( numBSPShaders * sizeof( bspShader_t ) ) ); + Sys_Printf( "%9d brushes %9d\n", + numBSPBrushes, (int) ( numBSPBrushes * sizeof( bspBrush_t ) ) ); + Sys_Printf( "%9d brushsides %9d *\n", + numBSPBrushSides, (int) ( numBSPBrushSides * sizeof( bspBrushSide_t ) ) ); + Sys_Printf( "%9d fogs %9d\n", + numBSPFogs, (int) ( numBSPFogs * sizeof( bspFog_t ) ) ); + Sys_Printf( "%9d planes %9d\n", + numBSPPlanes, (int) ( numBSPPlanes * sizeof( bspPlane_t ) ) ); + Sys_Printf( "%9d entdata %9d\n", + numEntities, bspEntDataSize ); + Sys_Printf( "\n" ); + + Sys_Printf( "%9d nodes %9d\n", + numBSPNodes, (int) ( numBSPNodes * sizeof( bspNode_t ) ) ); + Sys_Printf( "%9d leafs %9d\n", + numBSPLeafs, (int) ( numBSPLeafs * sizeof( bspLeaf_t ) ) ); + Sys_Printf( "%9d leafsurfaces %9d\n", + numBSPLeafSurfaces, (int) ( numBSPLeafSurfaces * sizeof( *bspLeafSurfaces ) ) ); + Sys_Printf( "%9d leafbrushes %9d\n", + numBSPLeafBrushes, (int) ( numBSPLeafBrushes * sizeof( *bspLeafBrushes ) ) ); + Sys_Printf( "\n" ); + + Sys_Printf( "%9d drawsurfaces %9d *\n", + numBSPDrawSurfaces, (int) ( numBSPDrawSurfaces * sizeof( *bspDrawSurfaces ) ) ); + Sys_Printf( "%9d drawverts %9d *\n", + numBSPDrawVerts, (int) ( numBSPDrawVerts * sizeof( *bspDrawVerts ) ) ); + Sys_Printf( "%9d drawindexes %9d\n", + numBSPDrawIndexes, (int) ( numBSPDrawIndexes * sizeof( *bspDrawIndexes ) ) ); + Sys_Printf( "\n" ); + + Sys_Printf( "%9d lightmaps %9d\n", + numBSPLightBytes / ( game->lightmapSize * game->lightmapSize * 3 ), numBSPLightBytes ); + Sys_Printf( "%9d lightgrid %9d *\n", + numBSPGridPoints, (int) ( numBSPGridPoints * sizeof( *bspGridPoints ) ) ); + Sys_Printf( " visibility %9d\n", + numBSPVisBytes ); + + if (bspx) + { + size_t i; + Sys_Printf( "\n" ); + for (i = 0; i < bspx->numlumps; i++) + { //print counts only for known lumps + if (!strcmp(bspx->lumps[i].lumpname, "ENVMAP")) + Sys_Printf( "%9d %-13s %9d\n", bspx->lumps[i].lumpsize/sizeof(denvmap_t), bspx->lumps[i].lumpname, bspx->lumps[i].lumpsize); + else if (!strcmp(bspx->lumps[i].lumpname, "SURFENVMAP")) + Sys_Printf( "%9d %-13s %9d\n", bspx->lumps[i].lumpsize/sizeof(int), bspx->lumps[i].lumpname, bspx->lumps[i].lumpsize); + else + Sys_Printf( " %-13s %9d\n", bspx->lumps[i].lumpname, bspx->lumps[i].lumpsize); + } + } +} + + + +/* ------------------------------------------------------------------------------- + + entity data handling + + ------------------------------------------------------------------------------- */ + + +/* + StripTrailing() + strips low byte chars off the end of a string + */ + +void StripTrailing( char *e ){ + char *s; + + + s = e + strlen( e ) - 1; + while ( s >= e && *s <= 32 ) + { + *s = 0; + s--; + } +} + + + +/* + ParseEpair() + parses a single quoted "key" "value" pair into an epair struct + */ + +epair_t *ParseEPair( void ){ + epair_t *e; + + + /* allocate and clear new epair */ + e = safe_malloc( sizeof( epair_t ) ); + memset( e, 0, sizeof( epair_t ) ); + + /* handle key */ + if ( strlen( token ) >= ( MAX_KEY - 1 ) ) { + Error( "ParseEPair: token too long" ); + } + + e->key = copystring( token ); + GetToken( qfalse ); + + /* handle value */ + if ( strlen( token ) >= MAX_VALUE - 1 ) { + Error( "ParseEpar: token too long" ); + } + e->value = copystring( token ); + + /* strip trailing spaces that sometimes get accidentally added in the editor */ + StripTrailing( e->key ); + StripTrailing( e->value ); + + /* return it */ + return e; +} + + + +/* + ParseEntity() + parses an entity's epairs + */ + +qboolean ParseEntity( void ){ + epair_t *e; + + + /* dummy check */ + if ( !GetToken( qtrue ) ) { + return qfalse; + } + if ( strcmp( token, "{" ) ) { + Error( "ParseEntity: { not found" ); + } + AUTOEXPAND_BY_REALLOC( entities, numEntities, allocatedEntities, 32 ); + + /* create new entity */ + mapEnt = &entities[ numEntities ]; + numEntities++; + memset( mapEnt, 0, sizeof( *mapEnt ) ); + + /* parse */ + while ( 1 ) + { + if ( !GetToken( qtrue ) ) { + Error( "ParseEntity: EOF without closing brace" ); + } + if ( !EPAIR_STRCMP( token, "}" ) ) { + break; + } + e = ParseEPair(); + e->next = mapEnt->epairs; + mapEnt->epairs = e; + } + + /* return to sender */ + return qtrue; +} + + + +/* + ParseEntities() + parses the bsp entity data string into entities + */ + +void ParseEntities( void ){ + numEntities = 0; + ParseFromMemory( bspEntData, bspEntDataSize ); + while ( ParseEntity() ) ; + + /* ydnar: set number of bsp entities in case a map is loaded on top */ + numBSPEntities = numEntities; +} + + + +/* + * must be called before UnparseEntities + */ +void InjectCommandLine( char **argv, int beginArgs, int endArgs ){ + const char *previousCommandLine; + char newCommandLine[1024]; + const char *inpos; + char *outpos = newCommandLine; + char *sentinel = newCommandLine + sizeof( newCommandLine ) - 1; + int i; + + previousCommandLine = ValueForKey( &entities[0], "_q3map2_cmdline" ); + if ( previousCommandLine && *previousCommandLine ) { + inpos = previousCommandLine; + while ( outpos != sentinel && *inpos ) + *outpos++ = *inpos++; + if ( outpos != sentinel ) { + *outpos++ = ';'; + } + if ( outpos != sentinel ) { + *outpos++ = ' '; + } + } + + for ( i = beginArgs; i < endArgs; ++i ) + { + if ( outpos != sentinel && i != beginArgs ) { + *outpos++ = ' '; + } + inpos = argv[i]; + while ( outpos != sentinel && *inpos ) + if ( *inpos != '\\' && *inpos != '"' && *inpos != ';' && (unsigned char) *inpos >= ' ' ) { + *outpos++ = *inpos++; + } + } + + *outpos = 0; + /* TODO: Make this a switch */ + /*SetKeyValue( &entities[0], "_q3map2_cmdline", newCommandLine ); + SetKeyValue( &entities[0], "_q3map2_version", Q3MAP_VERSION );*/ +} + + + +/* + UnparseEntities() + generates the dentdata string from all the entities. + this allows the utilities to add or remove key/value + pairs to the data created by the map editor + */ + +void UnparseEntities( void ){ + int i; + char *buf, *end; + epair_t *ep; + char line[ 2048 ]; + char key[ 1024 ], value[ 1024 ]; + const char *value2; + + + /* setup */ + AUTOEXPAND_BY_REALLOC( bspEntData, 0, allocatedBSPEntData, 1024 ); + buf = bspEntData; + end = buf; + *end = 0; + + + /* run through entity list */ + for ( i = 0; i < numBSPEntities && i < numEntities; i++ ) + { + { + int sz = end - buf; + AUTOEXPAND_BY_REALLOC( bspEntData, sz + 65536, allocatedBSPEntData, 1024 ); + buf = bspEntData; + end = buf + sz; + } + + /* get epair */ + ep = entities[ i ].epairs; + if ( ep == NULL ) { + continue; /* ent got removed */ + + } + /* ydnar: certain entities get stripped from bsp file */ + value2 = ValueForKey( &entities[ i ], "classname" ); + if ( !Q_stricmp( value2, "misc_model" ) || + !Q_stricmp( value2, "prop_static" ) || + !Q_stricmp( value2, "_decal" ) || + !Q_stricmp( value2, "_skybox" ) ) { + continue; + } + + /* add beginning brace */ + strcat( end, "{\n" ); + end += 2; + + /* walk epair list */ + for ( ep = entities[ i ].epairs; ep != NULL; ep = ep->next ) + { + /* copy and clean */ + strcpy( key, ep->key ); + StripTrailing( key ); + strcpy( value, ep->value ); + StripTrailing( value ); + + /* add to buffer */ + sprintf( line, "\"%s\" \"%s\"\n", key, value ); + strcat( end, line ); + end += strlen( line ); + } + + /* add trailing brace */ + strcat( end,"}\n" ); + end += 2; + + /* check for overflow */ + if ( end > buf + allocatedBSPEntData ) { + Error( "Entity text too long" ); + } + } + + /* set size */ + bspEntDataSize = end - buf + 1; +} + + + +/* + PrintEntity() + prints an entity's epairs to the console + */ + +void PrintEntity( const entity_t *ent ){ + epair_t *ep; + + + Sys_Printf( "------- entity %p -------\n", ent ); + for ( ep = ent->epairs; ep != NULL; ep = ep->next ) + Sys_Printf( "%s = %s\n", ep->key, ep->value ); + +} + + + +/* + SetKeyValue() + sets an epair in an entity + */ + +void SetKeyValue( entity_t *ent, const char *key, const char *value ){ + epair_t *ep; + + + /* check for existing epair */ + for ( ep = ent->epairs; ep != NULL; ep = ep->next ) + { + if ( !EPAIR_STRCMP( ep->key, key ) ) { + free( ep->value ); + ep->value = copystring( value ); + return; + } + } + + /* create new epair */ + ep = safe_malloc( sizeof( *ep ) ); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring( key ); + ep->value = copystring( value ); +} + + + +/* + KeyExists() + returns true if entity has this key + */ + +qboolean KeyExists( const entity_t *ent, const char *key ){ + epair_t *ep; + + /* walk epair list */ + for ( ep = ent->epairs; ep != NULL; ep = ep->next ) + { + if ( !EPAIR_STRCMP( ep->key, key ) ) { + return qtrue; + } + } + + /* no match */ + return qfalse; +} + + + +/* + ValueForKey() + gets the value for an entity key + */ + +const char *ValueForKey( const entity_t *ent, const char *key ){ + epair_t *ep; + + + /* dummy check */ + if ( ent == NULL ) { + return ""; + } + + /* walk epair list */ + for ( ep = ent->epairs; ep != NULL; ep = ep->next ) + { + if ( !EPAIR_STRCMP( ep->key, key ) ) { + return ep->value; + } + } + + /* if no match, return empty string */ + return ""; +} + + + +/* + IntForKey() + gets the integer point value for an entity key + */ + +int IntForKey( const entity_t *ent, const char *key ){ + const char *k; + + + k = ValueForKey( ent, key ); + return atoi( k ); +} + + + +/* + FloatForKey() + gets the floating point value for an entity key + */ + +vec_t FloatForKey( const entity_t *ent, const char *key ){ + const char *k; + + + k = ValueForKey( ent, key ); + return atof( k ); +} + + + +/* + GetVectorForKey() + gets a 3-element vector value for an entity key + */ + +qboolean GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ){ + const char *k; + double v1, v2, v3; + + + /* get value */ + k = ValueForKey( ent, key ); + + /* scanf into doubles, then assign, so it is vec_t size independent */ + v1 = v2 = v3 = 0.0; + sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 ); + vec[ 0 ] = v1; + vec[ 1 ] = v2; + vec[ 2 ] = v3; + + /* true if the key is found, false otherwise */ + return strlen( k ); +} + + + +/* + FindTargetEntity() + finds an entity target + */ + +entity_t *FindTargetEntity( const char *target ){ + int i; + const char *n; + + + /* walk entity list */ + for ( i = 0; i < numEntities; i++ ) + { + n = ValueForKey( &entities[ i ], "targetname" ); + if ( !strcmp( n, target ) ) { + return &entities[ i ]; + } + } + + /* nada */ + return NULL; +} + + + +/* + GetEntityShadowFlags() - ydnar + gets an entity's shadow flags + note: does not set them to defaults if the keys are not found! + */ + +void GetEntityShadowFlags( const entity_t *ent, const entity_t *ent2, int *castShadows, int *recvShadows ){ + const char *value; + + /* get cast shadows */ + if ( castShadows != NULL ) { + value = ValueForKey( ent, "_castShadows" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( ent, "_cs" ); + } + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( ent2, "_castShadows" ); + } + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( ent2, "_cs" ); + } + if ( value[ 0 ] != '\0' ) { + *castShadows = atoi( value ); + } + } + + /* receive */ + if ( recvShadows != NULL ) { + value = ValueForKey( ent, "_receiveShadows" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( ent, "_rs" ); + } + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( ent2, "_receiveShadows" ); + } + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( ent2, "_rs" ); + } + if ( value[ 0 ] != '\0' ) { + *recvShadows = atoi( value ); + } + } + + /* vortex: game-specific default eneity keys */ + value = ValueForKey( ent, "classname" ); + if ( !Q_stricmp( game->magic, "dq" ) || !Q_stricmp( game->magic, "prophecy" ) ) { + /* vortex: deluxe quake default shadow flags */ + if ( !Q_stricmp( value, "func_wall" ) ) { + if ( recvShadows != NULL ) { + *recvShadows = 1; + } + if ( castShadows != NULL ) { + *castShadows = 1; + } + } + } +} diff --git a/tools/vmap/bspfile_ibsp.c b/tools/vmap/bspfile_ibsp.c new file mode 100644 index 0000000..82e9e11 --- /dev/null +++ b/tools/vmap/bspfile_ibsp.c @@ -0,0 +1,574 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSPFILE_IBSP_C + + + +/* dependencies */ +#include "vmap.h" + + + + +/* ------------------------------------------------------------------------------- + + this file handles translating the bsp file format used by quake 3, rtcw, and ef + into the abstracted bsp file used by q3map2. + + ------------------------------------------------------------------------------- */ + +/* constants */ +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define LUMP_ADVERTISEMENTS 17 +#define HEADER_LUMPS 18 + + +/* types */ +typedef struct +{ + char ident[ 4 ]; + int version; + + bspLump_t lumps[ HEADER_LUMPS ]; +} +ibspHeader_t; + + + +/* brush sides */ +typedef struct +{ + int planeNum; + int shaderNum; +} +ibspBrushSide_t; + + +static void CopyBrushSidesLump( ibspHeader_t *header ){ + int i; + ibspBrushSide_t *in; + bspBrushSide_t *out; + + + /* get count */ + numBSPBrushSides = GetLumpElements( (bspHeader_t*) header, LUMP_BRUSHSIDES, sizeof( *in ) ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_BRUSHSIDES ); + for ( i = 0; i < numBSPBrushSides; i++ ) + { + AUTOEXPAND_BY_REALLOC( bspBrushSides, i, allocatedBSPBrushSides, 1024 ); + out = &bspBrushSides[i]; + out->planeNum = in->planeNum; + out->shaderNum = in->shaderNum; + out->surfaceNum = -1; + in++; + } +} + + +static void AddBrushSidesLump( FILE *file, ibspHeader_t *header ){ + int i, size; + bspBrushSide_t *in; + ibspBrushSide_t *buffer, *out; + + + /* allocate output buffer */ + size = numBSPBrushSides * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspBrushSides; + out = buffer; + for ( i = 0; i < numBSPBrushSides; i++ ) + { + out->planeNum = in->planeNum; + out->shaderNum = in->shaderNum; + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHSIDES, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + + +/* drawsurfaces */ +typedef struct ibspDrawSurface_s +{ + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[ 3 ]; + + int patchWidth; + int patchHeight; +} +ibspDrawSurface_t; + + +static void CopyDrawSurfacesLump( ibspHeader_t *header ){ + int i, j; + ibspDrawSurface_t *in; + bspDrawSurface_t *out; + + /* get count */ + numBSPDrawSurfaces = GetLumpElements( (bspHeader_t*) header, LUMP_SURFACES, sizeof( *in ) ); + SetDrawSurfaces( numBSPDrawSurfaces ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_SURFACES ); + out = bspDrawSurfaces; + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + out->shaderNum = in->shaderNum; + out->fogNum = in->fogNum; + out->surfaceType = in->surfaceType; + out->firstVert = in->firstVert; + out->numVerts = in->numVerts; + out->firstIndex = in->firstIndex; + out->numIndexes = in->numIndexes; + + out->lightmapStyles[ 0 ] = LS_NORMAL; + out->vertexStyles[ 0 ] = LS_NORMAL; + out->lightmapNum[ 0 ] = in->lightmapNum; + out->lightmapX[ 0 ] = in->lightmapX; + out->lightmapY[ 0 ] = in->lightmapY; + + for ( j = 1; j < MAX_LIGHTMAPS; j++ ) + { + out->lightmapStyles[ j ] = LS_NONE; + out->vertexStyles[ j ] = LS_NONE; + out->lightmapNum[ j ] = -3; + out->lightmapX[ j ] = 0; + out->lightmapY[ j ] = 0; + } + + out->lightmapWidth = in->lightmapWidth; + out->lightmapHeight = in->lightmapHeight; + + VectorCopy( in->lightmapOrigin, out->lightmapOrigin ); + VectorCopy( in->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] ); + VectorCopy( in->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] ); + VectorCopy( in->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] ); + + out->patchWidth = in->patchWidth; + out->patchHeight = in->patchHeight; + + in++; + out++; + } +} + + +static void AddDrawSurfacesLump( FILE *file, ibspHeader_t *header ){ + int i, size; + bspDrawSurface_t *in; + ibspDrawSurface_t *buffer, *out; + + /* allocate output buffer */ + size = numBSPDrawSurfaces * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspDrawSurfaces; + out = buffer; + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + out->shaderNum = in->shaderNum; + out->fogNum = in->fogNum; + out->surfaceType = in->surfaceType; + out->firstVert = in->firstVert; + out->numVerts = in->numVerts; + out->firstIndex = in->firstIndex; + out->numIndexes = in->numIndexes; + + out->lightmapNum = in->lightmapNum[ 0 ]; + out->lightmapX = in->lightmapX[ 0 ]; + out->lightmapY = in->lightmapY[ 0 ]; + out->lightmapWidth = in->lightmapWidth; + out->lightmapHeight = in->lightmapHeight; + + VectorCopy( in->lightmapOrigin, out->lightmapOrigin ); + VectorCopy( in->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] ); + VectorCopy( in->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] ); + VectorCopy( in->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] ); + + out->patchWidth = in->patchWidth; + out->patchHeight = in->patchHeight; + + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_SURFACES, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + + +/* drawverts */ +typedef struct +{ + vec3_t xyz; + float st[ 2 ]; + float lightmap[ 2 ]; + vec3_t normal; + byte color[ 4 ]; +} +ibspDrawVert_t; + + +static void CopyDrawVertsLump( ibspHeader_t *header ){ + int i; + ibspDrawVert_t *in; + bspDrawVert_t *out; + + + /* get count */ + numBSPDrawVerts = GetLumpElements( (bspHeader_t*) header, LUMP_DRAWVERTS, sizeof( *in ) ); + SetDrawVerts( numBSPDrawVerts ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_DRAWVERTS ); + out = bspDrawVerts; + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + VectorCopy( in->xyz, out->xyz ); + out->st[ 0 ] = in->st[ 0 ]; + out->st[ 1 ] = in->st[ 1 ]; + + out->lightmap[ 0 ][ 0 ] = in->lightmap[ 0 ]; + out->lightmap[ 0 ][ 1 ] = in->lightmap[ 1 ]; + + VectorCopy( in->normal, out->normal ); + + out->color[ 0 ][ 0 ] = in->color[ 0 ]; + out->color[ 0 ][ 1 ] = in->color[ 1 ]; + out->color[ 0 ][ 2 ] = in->color[ 2 ]; + out->color[ 0 ][ 3 ] = in->color[ 3 ]; + + in++; + out++; + } +} + + +static void AddDrawVertsLump( FILE *file, ibspHeader_t *header ){ + int i, size; + bspDrawVert_t *in; + ibspDrawVert_t *buffer, *out; + + + /* allocate output buffer */ + size = numBSPDrawVerts * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspDrawVerts; + out = buffer; + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + VectorCopy( in->xyz, out->xyz ); + out->st[ 0 ] = in->st[ 0 ]; + out->st[ 1 ] = in->st[ 1 ]; + + out->lightmap[ 0 ] = in->lightmap[ 0 ][ 0 ]; + out->lightmap[ 1 ] = in->lightmap[ 0 ][ 1 ]; + + VectorCopy( in->normal, out->normal ); + + out->color[ 0 ] = in->color[ 0 ][ 0 ]; + out->color[ 1 ] = in->color[ 0 ][ 1 ]; + out->color[ 2 ] = in->color[ 0 ][ 2 ]; + out->color[ 3 ] = in->color[ 0 ][ 3 ]; + + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_DRAWVERTS, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + + +/* light grid */ +typedef struct +{ + byte ambient[ 3 ]; + byte directed[ 3 ]; + byte latLong[ 2 ]; +} +ibspGridPoint_t; + + +static void CopyLightGridLumps( ibspHeader_t *header ){ + int i, j; + ibspGridPoint_t *in; + bspGridPoint_t *out; + + + /* get count */ + numBSPGridPoints = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTGRID, sizeof( *in ) ); + + /* allocate buffer */ + bspGridPoints = safe_malloc( numBSPGridPoints * sizeof( *bspGridPoints ) ); + memset( bspGridPoints, 0, numBSPGridPoints * sizeof( *bspGridPoints ) ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_LIGHTGRID ); + out = bspGridPoints; + for ( i = 0; i < numBSPGridPoints; i++ ) + { + for ( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + VectorCopy( in->ambient, out->ambient[ j ] ); + VectorCopy( in->directed, out->directed[ j ] ); + out->styles[ j ] = LS_NONE; + } + + out->styles[ 0 ] = LS_NORMAL; + + out->latLong[ 0 ] = in->latLong[ 0 ]; + out->latLong[ 1 ] = in->latLong[ 1 ]; + + in++; + out++; + } +} + + +static void AddLightGridLumps( FILE *file, ibspHeader_t *header ){ + int i; + bspGridPoint_t *in; + ibspGridPoint_t *buffer, *out; + + + /* dummy check */ + if ( bspGridPoints == NULL ) { + return; + } + + /* allocate temporary buffer */ + buffer = safe_malloc( numBSPGridPoints * sizeof( *out ) ); + + /* convert */ + in = bspGridPoints; + out = buffer; + for ( i = 0; i < numBSPGridPoints; i++ ) + { + VectorCopy( in->ambient[ 0 ], out->ambient ); + VectorCopy( in->directed[ 0 ], out->directed ); + + out->latLong[ 0 ] = in->latLong[ 0 ]; + out->latLong[ 1 ] = in->latLong[ 1 ]; + + in++; + out++; + } + + /* write lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTGRID, buffer, ( numBSPGridPoints * sizeof( *out ) ) ); + + /* free buffer (ydnar 2002-10-22: [bug 641] thanks Rap70r! */ + free( buffer ); +} + +/* + LoadIBSPFile() + loads a quake 3 bsp file into memory + */ + +void LoadIBSPFile( const char *filename ){ + ibspHeader_t *header; + + + /* load the file header */ + int flength = LoadFile( filename, (void**) &header ); + + /* swap the header (except the first 4 bytes) */ + SwapBlock( (int*) ( (byte*) header + sizeof( int ) ), sizeof( *header ) - sizeof( int ) ); + + /* make sure it matches the format we're trying to load */ + if ( force == qfalse && *( (int*) header->ident ) != *( (int*) game->bspIdent ) ) { + Error( "%s is not a %s file", filename, game->bspIdent ); + } + if ( force == qfalse && header->version != game->bspVersion ) { + Error( "%s is version %d, not %d", filename, header->version, game->bspVersion ); + } + + /* load/convert lumps */ + numBSPShaders = CopyLump_Allocate( (bspHeader_t*) header, LUMP_SHADERS, (void **) &bspShaders, sizeof( bspShader_t ), &allocatedBSPShaders ); + + numBSPModels = CopyLump_Allocate( (bspHeader_t*) header, LUMP_MODELS, (void **) &bspModels, sizeof( bspModel_t ), &allocatedBSPModels ); + + numBSPPlanes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_PLANES, (void **) &bspPlanes, sizeof( bspPlane_t ), &allocatedBSPPlanes ); + + numBSPLeafs = CopyLump( (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, sizeof( bspLeaf_t ) ); // TODO fix overflow + + numBSPNodes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_NODES, (void **) &bspNodes, sizeof( bspNode_t ), &allocatedBSPNodes ); + + numBSPLeafSurfaces = CopyLump_Allocate( (bspHeader_t*) header, LUMP_LEAFSURFACES, (void **) &bspLeafSurfaces, sizeof( bspLeafSurfaces[ 0 ] ), &allocatedBSPLeafSurfaces ); + + numBSPLeafBrushes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_LEAFBRUSHES, (void **) &bspLeafBrushes, sizeof( bspLeafBrushes[ 0 ] ), &allocatedBSPLeafBrushes ); + + numBSPBrushes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_BRUSHES, (void **) &bspBrushes, sizeof( bspBrush_t ), &allocatedBSPLeafBrushes ); + + CopyBrushSidesLump( header ); + + CopyDrawVertsLump( header ); + + CopyDrawSurfacesLump( header ); + + numBSPFogs = CopyLump( (bspHeader_t*) header, LUMP_FOGS, bspFogs, sizeof( bspFog_t ) ); // TODO fix overflow + + numBSPDrawIndexes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_DRAWINDEXES, (void **) &bspDrawIndexes, sizeof( bspDrawIndexes[ 0 ] ), &allocatedBSPDrawIndexes ); + + numBSPVisBytes = CopyLump( (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, 1 ); // TODO fix overflow + + numBSPLightBytes = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTMAPS, 1 ); // TODO change to CopyLump_Allocate + bspLightBytes = safe_malloc( numBSPLightBytes ); + CopyLump( (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, 1 ); + + bspEntDataSize = CopyLump_Allocate( (bspHeader_t*) header, LUMP_ENTITIES, (void **) &bspEntData, 1, &allocatedBSPEntData ); + + CopyLightGridLumps( header ); + + BSPX_Setup(header, flength, header->lumps, HEADER_LUMPS); + + /* free the file buffer */ + free( header ); +} + + + +/* + WriteIBSPFile() + writes an id bsp file + */ + +void WriteIBSPFile( const char *filename ){ + ibspHeader_t outheader, *header; + FILE *file; + time_t t; + char marker[ 1024 ]; + int size; + + + /* set header */ + header = &outheader; + memset( header, 0, sizeof( *header ) ); + + //% Swapfile(); + + /* set up header */ + *( (int*) (bspHeader_t*) header->ident ) = *( (int*) game->bspIdent ); + header->version = LittleLong( game->bspVersion ); + + /* write initial header */ + file = SafeOpenWrite( filename ); + SafeWrite( file, (bspHeader_t*) header, sizeof( *header ) ); /* overwritten later */ + + /* add lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_SHADERS, bspShaders, numBSPShaders * sizeof( bspShader_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_PLANES, bspPlanes, numBSPPlanes * sizeof( bspPlane_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, numBSPLeafs * sizeof( bspLeaf_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_NODES, bspNodes, numBSPNodes * sizeof( bspNode_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHES, bspBrushes, numBSPBrushes * sizeof( bspBrush_t ) ); + AddBrushSidesLump( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFSURFACES, bspLeafSurfaces, numBSPLeafSurfaces * sizeof( bspLeafSurfaces[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFBRUSHES, bspLeafBrushes, numBSPLeafBrushes * sizeof( bspLeafBrushes[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_MODELS, bspModels, numBSPModels * sizeof( bspModel_t ) ); + AddDrawVertsLump( file, header ); + AddDrawSurfacesLump( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, numBSPVisBytes ); + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, numBSPLightBytes ); + AddLightGridLumps( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_ENTITIES, bspEntData, bspEntDataSize ); + AddLump( file, (bspHeader_t*) header, LUMP_FOGS, bspFogs, numBSPFogs * sizeof( bspFog_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_DRAWINDEXES, bspDrawIndexes, numBSPDrawIndexes * sizeof( bspDrawIndexes[ 0 ] ) ); + + BSPX_WriteLumps( file, header->lumps, HEADER_LUMPS); + + /* emit bsp size */ + size = ftell( file ); + Sys_Printf( "Wrote %.1f MB (%d bytes)\n", (float) size / ( 1024 * 1024 ), size ); + + /* write the completed header */ + fseek( file, 0, SEEK_SET ); + SafeWrite( file, header, sizeof( *header ) ); + + /* close the file */ + fclose( file ); +} diff --git a/tools/vmap/bspfile_rbsp.c b/tools/vmap/bspfile_rbsp.c new file mode 100644 index 0000000..18b44c3 --- /dev/null +++ b/tools/vmap/bspfile_rbsp.c @@ -0,0 +1,553 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSPFILE_RBSP_C + + + +/* dependencies */ +#include "vmap.h" + + + + +/* ------------------------------------------------------------------------------- + + this file handles translating the bsp file format used by quake 3, rtcw, and ef + into the abstracted bsp file used by q3map2. + + ------------------------------------------------------------------------------- */ + +/* constants */ +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define LUMP_LIGHTARRAY 17 +#define HEADER_LUMPS 18 + + +/* types */ +typedef struct +{ + char ident[ 4 ]; + int version; + + bspLump_t lumps[ HEADER_LUMPS ]; +} +rbspHeader_t; + + + +/* light grid */ +#define MAX_MAP_GRID 0xffff +#define MAX_MAP_GRIDARRAY 0x100000 +#define LG_EPSILON 4 + + +static void CopyLightGridLumps( rbspHeader_t *header ){ + int i; + unsigned short *inArray; + bspGridPoint_t *in, *out; + + + /* get count */ + numBSPGridPoints = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTARRAY, sizeof( *inArray ) ); + + /* allocate buffer */ + bspGridPoints = safe_malloc( numBSPGridPoints * sizeof( *bspGridPoints ) ); + memset( bspGridPoints, 0, numBSPGridPoints * sizeof( *bspGridPoints ) ); + + /* copy */ + inArray = GetLump( (bspHeader_t*) header, LUMP_LIGHTARRAY ); + in = GetLump( (bspHeader_t*) header, LUMP_LIGHTGRID ); + out = bspGridPoints; + for ( i = 0; i < numBSPGridPoints; i++ ) + { + memcpy( out, &in[ *inArray ], sizeof( *in ) ); + inArray++; + out++; + } +} + + +static void AddLightGridLumps( FILE *file, rbspHeader_t *header ){ + int i, j, k, c, d; + int numGridPoints, maxGridPoints; + bspGridPoint_t *gridPoints, *in, *out; + int numGridArray; + unsigned short *gridArray; + qboolean bad; + + + /* allocate temporary buffers */ + maxGridPoints = ( numBSPGridPoints < MAX_MAP_GRID ) ? numBSPGridPoints : MAX_MAP_GRID; + gridPoints = safe_malloc( maxGridPoints * sizeof( *gridPoints ) ); + gridArray = safe_malloc( numBSPGridPoints * sizeof( *gridArray ) ); + + /* zero out */ + numGridPoints = 0; + numGridArray = numBSPGridPoints; + + /* for each bsp grid point, find an approximate twin */ + Sys_Printf( "Storing lightgrid: %d points\n", numBSPGridPoints ); + for ( i = 0; i < numGridArray; i++ ) + { + /* get points */ + in = &bspGridPoints[ i ]; + + /* walk existing list */ + for ( j = 0; j < numGridPoints; j++ ) + { + /* get point */ + out = &gridPoints[ j ]; + + /* compare styles */ + if ( memcmp( in->styles, out->styles, MAX_LIGHTMAPS ) ) { + continue; + } + + /* compare direction */ + d = abs( in->latLong[ 0 ] - out->latLong[ 0 ] ); + if ( d < ( 255 - LG_EPSILON ) && d > LG_EPSILON ) { + continue; + } + d = abs( in->latLong[ 1 ] - out->latLong[ 1 ] ); + if ( d < 255 - LG_EPSILON && d > LG_EPSILON ) { + continue; + } + + /* compare light */ + bad = qfalse; + for ( k = 0; ( k < MAX_LIGHTMAPS && bad == qfalse ); k++ ) + { + for ( c = 0; c < 3; c++ ) + { + if ( abs( (int) in->ambient[ k ][ c ] - (int) out->ambient[ k ][ c ] ) > LG_EPSILON || + abs( (int) in->directed[ k ][ c ] - (int) out->directed[ k ][ c ] ) > LG_EPSILON ) { + bad = qtrue; + break; + } + } + } + + /* failure */ + if ( bad ) { + continue; + } + + /* this sample is ok */ + break; + } + + /* set sample index */ + gridArray[ i ] = (unsigned short) j; + + /* if no sample found, add a new one */ + if ( j >= numGridPoints && numGridPoints < maxGridPoints ) { + out = &gridPoints[ numGridPoints++ ]; + memcpy( out, in, sizeof( *in ) ); + } + } + + /* swap array */ + for ( i = 0; i < numGridArray; i++ ) + gridArray[ i ] = LittleShort( gridArray[ i ] ); + + /* write lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTGRID, gridPoints, ( numGridPoints * sizeof( *gridPoints ) ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTARRAY, gridArray, ( numGridArray * sizeof( *gridArray ) ) ); + + /* free buffers */ + free( gridPoints ); + free( gridArray ); +} + + +/* drawsurfaces */ +typedef struct rbspDrawSurface_s +{ + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + byte lightmapStyles[MAX_LIGHTMAPS]; + byte vertexStyles[MAX_LIGHTMAPS]; + + int lightmapNum[MAX_LIGHTMAPS]; + int lightmap_offs[2][MAX_LIGHTMAPS]; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[ 3 ]; + + int patchWidth; + int patchHeight; +} +rbspDrawSurface_t; + + +static void CopyDrawSurfacesLump( rbspHeader_t *header ){ + int i, y, j; + rbspDrawSurface_t *in; + bspDrawSurface_t *out; + + /* get count */ + numBSPDrawSurfaces = GetLumpElements( (bspHeader_t*) header, LUMP_SURFACES, sizeof( *in ) ); + SetDrawSurfaces( numBSPDrawSurfaces ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_SURFACES ); + out = bspDrawSurfaces; + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + out->shaderNum = in->shaderNum; + out->fogNum = in->fogNum; + out->surfaceType = in->surfaceType; + out->firstVert = in->firstVert; + out->numVerts = in->numVerts; + out->firstIndex = in->firstIndex; + out->numIndexes = in->numIndexes; + + for ( y = 0; y < MAX_LIGHTMAPS; y++) { + out->lightmapStyles[ y ] = in->lightmapStyles[y]; + out->vertexStyles[ y ] = in->vertexStyles[y]; + out->lightmapNum[ y ] = in->lightmapNum[y]; + out->lightmapX[ y ] = in->lightmap_offs[0][y]; + out->lightmapY[ y ] = in->lightmap_offs[1][y]; + } + + out->lightmapWidth = in->lightmapWidth; + out->lightmapHeight = in->lightmapHeight; + + VectorCopy( in->lightmapOrigin, out->lightmapOrigin ); + VectorCopy( in->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] ); + VectorCopy( in->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] ); + VectorCopy( in->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] ); + + out->patchWidth = in->patchWidth; + out->patchHeight = in->patchHeight; + + in++; + out++; + } +} + + +static void AddDrawSurfacesLump( FILE *file, rbspHeader_t *header ){ + int i, y, size; + bspDrawSurface_t *in; + rbspDrawSurface_t *buffer, *out; + + /* allocate output buffer */ + size = numBSPDrawSurfaces * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspDrawSurfaces; + out = buffer; + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + out->shaderNum = in->shaderNum; + out->fogNum = in->fogNum; + out->surfaceType = in->surfaceType; + out->firstVert = in->firstVert; + out->numVerts = in->numVerts; + out->firstIndex = in->firstIndex; + out->numIndexes = in->numIndexes; + + for ( y = 0; y < MAX_LIGHTMAPS; y++) { + out->lightmapNum[y] = in->lightmapNum[ y ]; + out->lightmap_offs[0][y] = in->lightmapX[ y ]; + out->lightmap_offs[1][y] = in->lightmapY[ y ]; + out->lightmapStyles[y] = in->lightmapStyles[ y ]; + out->vertexStyles[y] = in->vertexStyles[ y ]; + } + + out->lightmapWidth = in->lightmapWidth; + out->lightmapHeight = in->lightmapHeight; + + VectorCopy( in->lightmapOrigin, out->lightmapOrigin ); + VectorCopy( in->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] ); + VectorCopy( in->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] ); + VectorCopy( in->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] ); + + out->patchWidth = in->patchWidth; + out->patchHeight = in->patchHeight; + + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_SURFACES, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + +/* drawverts */ +typedef struct +{ + vec3_t xyz; + float st[ 2 ]; + float lightmap[ MAX_LIGHTMAPS ][ 2 ]; + vec3_t normal; + byte color[ MAX_LIGHTMAPS ][ 4 ]; +} +rbspDrawVert_t; + +static void CopyDrawVertsLump( rbspHeader_t *header ){ + int i, y; + rbspDrawVert_t *in; + bspDrawVert_t *out; + + + /* get count */ + numBSPDrawVerts = GetLumpElements( (bspHeader_t*) header, LUMP_DRAWVERTS, sizeof( *in ) ); + SetDrawVerts( numBSPDrawVerts ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_DRAWVERTS ); + out = bspDrawVerts; + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + VectorCopy( in->xyz, out->xyz ); + out->st[ 0 ] = in->st[ 0 ]; + out->st[ 1 ] = in->st[ 1 ]; + + VectorCopy( in->normal, out->normal ); + + for (y = 0; y < MAX_LIGHTMAPS; y++) { + out->color[ y ][ 0 ] = in->color[ y ][ 0 ]; + out->color[ y ][ 1 ] = in->color[ y ][ 1 ]; + out->color[ y ][ 2 ] = in->color[ y ][ 2 ]; + out->color[ y ][ 3 ] = in->color[ y ][ 3 ]; + out->lightmap[ y ][ 0 ] = in->lightmap[ y ][ 0 ]; + out->lightmap[ y ][ 1 ] = in->lightmap[ y ][ 1 ]; + } + + in++; + out++; + } +} + + +static void AddDrawVertsLump( FILE *file, rbspHeader_t *header ){ + int i, y, size; + bspDrawVert_t *in; + rbspDrawVert_t *buffer, *out; + + + /* allocate output buffer */ + size = numBSPDrawVerts * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspDrawVerts; + out = buffer; + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + VectorCopy( in->xyz, out->xyz ); + out->st[ 0 ] = in->st[ 0 ]; + out->st[ 1 ] = in->st[ 1 ]; + + VectorCopy( in->normal, out->normal ); + + for (y = 0; y < MAX_LIGHTMAPS; y++) { + out->color[ y ][ 0 ] = in->color[ y ][ 0 ]; + out->color[ y ][ 1 ] = in->color[ y ][ 1 ]; + out->color[ y ][ 2 ] = in->color[ y ][ 2 ]; + out->color[ y ][ 3 ] = in->color[ y ][ 3 ]; + out->lightmap[ y ][ 0 ] = in->lightmap[ y ][ 0 ]; + out->lightmap[ y ][ 1 ] = in->lightmap[ y ][ 1 ]; + } + + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_DRAWVERTS, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + +/* + LoadRBSPFile() + loads a raven bsp file into memory + */ + +void LoadRBSPFile( const char *filename ){ + rbspHeader_t *header; + + + /* load the file header */ + int flength = LoadFile( filename, (void**) &header ); + + /* swap the header (except the first 4 bytes) */ + SwapBlock( (int*) ( (byte*) header + sizeof( int ) ), sizeof( *header ) - sizeof( int ) ); + + /* make sure it matches the format we're trying to load */ + if ( force == qfalse && *( (int*) header->ident ) != *( (int*) game->bspIdent ) ) { + Error( "%s is not a %s file", filename, game->bspIdent ); + } + if ( force == qfalse && header->version != game->bspVersion ) { + Error( "%s is version %d, not %d", filename, header->version, game->bspVersion ); + } + + /* load/convert lumps */ + numBSPShaders = CopyLump_Allocate( (bspHeader_t*) header, LUMP_SHADERS, (void **) &bspShaders, sizeof( bspShader_t ), &allocatedBSPShaders ); + + numBSPModels = CopyLump_Allocate( (bspHeader_t*) header, LUMP_MODELS, (void **) &bspModels, sizeof( bspModel_t ), &allocatedBSPModels ); + + numBSPPlanes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_PLANES, (void **) &bspPlanes, sizeof( bspPlane_t ), &allocatedBSPPlanes ); + + numBSPLeafs = CopyLump( (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, sizeof( bspLeaf_t ) ); + + numBSPNodes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_NODES, (void **) &bspNodes, sizeof( bspNode_t ), &allocatedBSPNodes ); + + numBSPLeafSurfaces = CopyLump_Allocate( (bspHeader_t*) header, LUMP_LEAFSURFACES, (void **) &bspLeafSurfaces, sizeof( bspLeafSurfaces[ 0 ] ), &allocatedBSPLeafSurfaces ); + + numBSPLeafBrushes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_LEAFBRUSHES, (void **) &bspLeafBrushes, sizeof( bspLeafBrushes[ 0 ] ), &allocatedBSPLeafBrushes ); + + numBSPBrushes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_BRUSHES, (void **) &bspBrushes, sizeof( bspBrush_t ), &allocatedBSPLeafBrushes ); + + numBSPBrushSides = CopyLump_Allocate( (bspHeader_t*) header, LUMP_BRUSHSIDES, (void **) &bspBrushSides, sizeof( bspBrushSide_t ), &allocatedBSPBrushSides ); + + CopyDrawVertsLump( header ); + CopyDrawSurfacesLump( header ); + + numBSPFogs = CopyLump( (bspHeader_t*) header, LUMP_FOGS, bspFogs, sizeof( bspFogs[ 0 ] ) ); + + numBSPDrawIndexes = CopyLump_Allocate( (bspHeader_t*) header, LUMP_DRAWINDEXES, (void **) &bspDrawIndexes, sizeof( bspDrawIndexes[ 0 ] ), &allocatedBSPDrawIndexes ); + + numBSPVisBytes = CopyLump( (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, 1 ); + + numBSPLightBytes = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTMAPS, 1 ); + bspLightBytes = safe_malloc( numBSPLightBytes ); + CopyLump( (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, 1 ); + + bspEntDataSize = CopyLump_Allocate( (bspHeader_t*) header, LUMP_ENTITIES, (void **) &bspEntData, 1, &allocatedBSPEntData ); + + CopyLightGridLumps( header ); + + BSPX_Setup(header, flength, header->lumps, HEADER_LUMPS); + + /* free the file buffer */ + free( header ); +} + + + +/* + WriteRBSPFile() + writes a raven bsp file (modified by Vera Visions to support patchDefWS) + */ + +void WriteRBSPFile( const char *filename ){ + rbspHeader_t outheader, *header; + FILE *file; + time_t t; + char marker[ 1024 ]; + int size; + + + /* set header */ + header = &outheader; + memset( header, 0, sizeof( *header ) ); + + //% Swapfile(); + + /* set up header */ + *( (int*) (bspHeader_t*) header->ident ) = *( (int*) game->bspIdent ); + header->version = LittleLong( game->bspVersion ); + + /* write initial header */ + file = SafeOpenWrite( filename ); + SafeWrite( file, (bspHeader_t*) header, sizeof( *header ) ); /* overwritten later */ + + /* add lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_SHADERS, bspShaders, numBSPShaders * sizeof( bspShader_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_PLANES, bspPlanes, numBSPPlanes * sizeof( bspPlane_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, numBSPLeafs * sizeof( bspLeaf_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_NODES, bspNodes, numBSPNodes * sizeof( bspNode_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHES, bspBrushes, numBSPBrushes * sizeof( bspBrush_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHSIDES, bspBrushSides, numBSPBrushSides * sizeof( bspBrushSides[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFSURFACES, bspLeafSurfaces, numBSPLeafSurfaces * sizeof( bspLeafSurfaces[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFBRUSHES, bspLeafBrushes, numBSPLeafBrushes * sizeof( bspLeafBrushes[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_MODELS, bspModels, numBSPModels * sizeof( bspModel_t ) ); + AddDrawVertsLump( file, header ); + AddDrawSurfacesLump( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, numBSPVisBytes ); + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, numBSPLightBytes ); + AddLightGridLumps( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_ENTITIES, bspEntData, bspEntDataSize ); + AddLump( file, (bspHeader_t*) header, LUMP_FOGS, bspFogs, numBSPFogs * sizeof( bspFog_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_DRAWINDEXES, bspDrawIndexes, numBSPDrawIndexes * sizeof( bspDrawIndexes[ 0 ] ) ); + + BSPX_WriteLumps( file, header->lumps, HEADER_LUMPS); + + /* emit bsp size */ + size = ftell( file ); + Sys_Printf( "Wrote %.1f MB (%d bytes)\n", (float) size / ( 1024 * 1024 ), size ); + + /* write the completed header */ + fseek( file, 0, SEEK_SET ); + SafeWrite( file, header, sizeof( *header ) ); + + /* close the file */ + fclose( file ); +} diff --git a/tools/vmap/convert_ase.c b/tools/vmap/convert_ase.c new file mode 100644 index 0000000..a40148e --- /dev/null +++ b/tools/vmap/convert_ase.c @@ -0,0 +1,441 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define CONVERT_ASE_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + ConvertSurface() + converts a bsp drawsurface to an ase chunk + */ + +int numLightmapsASE = 0; +static void ConvertSurface( FILE *f, bspModel_t *model, int modelNum, bspDrawSurface_t *ds, int surfaceNum, vec3_t origin ){ + int i, v, face, a, b, c; + bspDrawVert_t *dv; + vec3_t normal; + char name[ 1024 ]; + + + /* ignore patches for now */ + if ( ds->surfaceType != MST_PLANAR && ds->surfaceType != MST_TRIANGLE_SOUP ) { + return; + } + + /* print object header for each dsurf */ + sprintf( name, "mat%dmodel%dsurf%d", ds->shaderNum, modelNum, surfaceNum ); + fprintf( f, "*GEOMOBJECT\t{\r\n" ); + fprintf( f, "\t*NODE_NAME\t\"%s\"\r\n", name ); + fprintf( f, "\t*NODE_TM\t{\r\n" ); + fprintf( f, "\t\t*NODE_NAME\t\"%s\"\r\n", name ); + fprintf( f, "\t\t*INHERIT_POS\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*INHERIT_ROT\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*INHERIT_SCL\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*TM_ROW0\t1.0\t0\t0\r\n" ); + fprintf( f, "\t\t*TM_ROW1\t0\t1.0\t0\r\n" ); + fprintf( f, "\t\t*TM_ROW2\t0\t0\t1.0\r\n" ); + fprintf( f, "\t\t*TM_ROW3\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*TM_POS\t%f\t%f\t%f\r\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] ); + fprintf( f, "\t}\r\n" ); + + /* print mesh header */ + fprintf( f, "\t*MESH\t{\r\n" ); + fprintf( f, "\t\t*TIMEVALUE\t0\r\n" ); + fprintf( f, "\t\t*MESH_NUMVERTEX\t%d\r\n", ds->numVerts ); + fprintf( f, "\t\t*MESH_NUMFACES\t%d\r\n", ds->numIndexes / 3 ); + switch ( ds->surfaceType ) + { + case MST_PLANAR: + fprintf( f, "\t\t*COMMENT\t\"SURFACETYPE\tMST_PLANAR\"\r\n" ); + break; + case MST_TRIANGLE_SOUP: + fprintf( f, "\t\t*COMMENT\t\"SURFACETYPE\tMST_TRIANGLE_SOUP\"\r\n" ); + break; + } + + /* export vertex xyz */ + fprintf( f, "\t\t*MESH_VERTEX_LIST\t{\r\n" ); + for ( i = 0; i < ds->numVerts; i++ ) + { + v = i + ds->firstVert; + dv = &bspDrawVerts[ v ]; + fprintf( f, "\t\t\t*MESH_VERTEX\t%d\t%f\t%f\t%f\r\n", i, dv->xyz[ 0 ], dv->xyz[ 1 ], dv->xyz[ 2 ] ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export vertex normals */ + fprintf( f, "\t\t*MESH_NORMALS\t{\r\n" ); + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + face = ( i / 3 ); + a = bspDrawIndexes[ i + ds->firstIndex ]; + b = bspDrawIndexes[ i + ds->firstIndex + 1 ]; + c = bspDrawIndexes[ i + ds->firstIndex + 2 ]; + VectorCopy( bspDrawVerts[ a ].normal, normal ); + VectorAdd( normal, bspDrawVerts[ b ].normal, normal ); + VectorAdd( normal, bspDrawVerts[ c ].normal, normal ); + if ( VectorNormalize( normal, normal ) ) { + fprintf( f, "\t\t\t*MESH_FACENORMAL\t%d\t%f\t%f\t%f\r\n", face, normal[ 0 ], normal[ 1 ], normal[ 2 ] ); + } + } + for ( i = 0; i < ds->numVerts; i++ ) + { + v = i + ds->firstVert; + dv = &bspDrawVerts[ v ]; + fprintf( f, "\t\t\t*MESH_VERTEXNORMAL\t%d\t%f\t%f\t%f\r\n", i, dv->normal[ 0 ], dv->normal[ 1 ], dv->normal[ 2 ] ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export faces */ + fprintf( f, "\t\t*MESH_FACE_LIST\t{\r\n" ); + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + face = ( i / 3 ); + a = bspDrawIndexes[ i + ds->firstIndex ]; + c = bspDrawIndexes[ i + ds->firstIndex + 1 ]; + b = bspDrawIndexes[ i + ds->firstIndex + 2 ]; + fprintf( f, "\t\t\t*MESH_FACE\t%d\tA:\t%d\tB:\t%d\tC:\t%d\tAB:\t1\tBC:\t1\tCA:\t1\t*MESH_SMOOTHING\t0\t*MESH_MTLID\t0\r\n", + face, a, b, c ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export vertex st */ + fprintf( f, "\t\t*MESH_NUMTVERTEX\t%d\r\n", ds->numVerts ); + fprintf( f, "\t\t*MESH_TVERTLIST\t{\r\n" ); + for ( i = 0; i < ds->numVerts; i++ ) + { + v = i + ds->firstVert; + dv = &bspDrawVerts[ v ]; + fprintf( f, "\t\t\t*MESH_TVERT\t%d\t%f\t%f\t%f\r\n", i, dv->st[ 0 ], ( 1.0 - dv->st[ 1 ] ), 1.0f ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export texture faces */ + fprintf( f, "\t\t*MESH_NUMTVFACES\t%d\r\n", ds->numIndexes / 3 ); + fprintf( f, "\t\t*MESH_TFACELIST\t{\r\n" ); + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + face = ( i / 3 ); + a = bspDrawIndexes[ i + ds->firstIndex ]; + c = bspDrawIndexes[ i + ds->firstIndex + 1 ]; + b = bspDrawIndexes[ i + ds->firstIndex + 2 ]; + fprintf( f, "\t\t\t*MESH_TFACE\t%d\t%d\t%d\t%d\r\n", face, a, b, c ); + } + fprintf( f, "\t\t}\r\n" ); + + /* print mesh footer */ + fprintf( f, "\t}\r\n" ); + + /* print object footer */ + fprintf( f, "\t*PROP_MOTIONBLUR\t0\r\n" ); + fprintf( f, "\t*PROP_CASTSHADOW\t1\r\n" ); + fprintf( f, "\t*PROP_RECVSHADOW\t1\r\n" ); + if ( lightmapsAsTexcoord ) { + if ( ds->lightmapNum[0] >= 0 && ds->lightmapNum[0] + (int)deluxemap < numLightmapsASE ) { + fprintf( f, "\t*MATERIAL_REF\t%d\r\n", ds->lightmapNum[0] + deluxemap ); + } + else{ + Sys_FPrintf( SYS_WRN, "WARNING: lightmap %d out of range, not exporting\n", ds->lightmapNum[0] + deluxemap ); + } + } + else{ + fprintf( f, "\t*MATERIAL_REF\t%d\r\n", ds->shaderNum ); + } + fprintf( f, "}\r\n" ); +} + + + +/* + ConvertModel() + exports a bsp model to an ase chunk + */ + +static void ConvertModel( FILE *f, bspModel_t *model, int modelNum, vec3_t origin ){ + int i, s; + bspDrawSurface_t *ds; + + + /* go through each drawsurf in the model */ + for ( i = 0; i < model->numBSPSurfaces; i++ ) + { + s = i + model->firstBSPSurface; + ds = &bspDrawSurfaces[ s ]; + ConvertSurface( f, model, modelNum, ds, s, origin ); + } +} + + + +/* + ConvertShader() + exports a bsp shader to an ase chunk + */ + +/* + *MATERIAL 0 { + *MATERIAL_NAME "models/test/rock16l" + *MATERIAL_CLASS "Standard" + *MATERIAL_AMBIENT 0.5882 0.5882 0.5882 + *MATERIAL_DIFFUSE 0.5882 0.5882 0.5882 + *MATERIAL_SPECULAR 0.5882 0.5882 0.5882 + *MATERIAL_SHINE 0.0000 + *MATERIAL_SHINESTRENGTH 0.0000 + *MATERIAL_TRANSPARENCY 0.0000 + *MATERIAL_WIRESIZE 1.0000 + *MATERIAL_SHADING Phong + *MATERIAL_XP_FALLOFF 0.0000 + *MATERIAL_SELFILLUM 0.0000 + *MATERIAL_FALLOFF In + *MATERIAL_XP_TYPE Filter + *MAP_DIFFUSE { + *MAP_NAME "Map #2" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0000 + *BITMAP "models/test/rock16l" + *MAP_TYPE Screen + *UVW_U_OFFSET 0.0000 + *UVW_V_OFFSET 0.0000 + *UVW_U_TILING 1.0000 + *UVW_V_TILING 1.0000 + *UVW_ANGLE 0.0000 + *UVW_BLUR 1.0000 + *UVW_BLUR_OFFSET 0.0000 + *UVW_NOUSE_AMT 1.0000 + *UVW_NOISE_SIZE 1.0000 + *UVW_NOISE_LEVEL 1 + *UVW_NOISE_PHASE 0.0000 + *BITMAP_FILTER Pyramidal + } + } + */ + +static void ConvertShader( FILE *f, bspShader_t *shader, int shaderNum ){ + shaderInfo_t *si; + char *c, filename[ 1024 ]; + + + /* get shader */ + si = ShaderInfoForShader( shader->shader ); + if ( si == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: NULL shader in BSP\n" ); + return; + } + + /* set bitmap filename */ + if ( si->shaderImage->filename[ 0 ] != '*' ) { + strcpy( filename, si->shaderImage->filename ); + } + else{ + sprintf( filename, "%s.tga", si->shader ); + } + for ( c = filename; *c != '\0'; c++ ) + if ( *c == '/' ) { + *c = '\\'; + } + + /* print shader info */ + fprintf( f, "\t*MATERIAL\t%d\t{\r\n", shaderNum ); + fprintf( f, "\t\t*MATERIAL_NAME\t\"%s\"\r\n", shader->shader ); + fprintf( f, "\t\t*MATERIAL_CLASS\t\"Standard\"\r\n" ); + fprintf( f, "\t\t*MATERIAL_DIFFUSE\t%f\t%f\t%f\r\n", si->color[ 0 ], si->color[ 1 ], si->color[ 2 ] ); + fprintf( f, "\t\t*MATERIAL_SHADING Phong\r\n" ); + + /* print map info */ + fprintf( f, "\t\t*MAP_DIFFUSE\t{\r\n" ); + fprintf( f, "\t\t\t*MAP_NAME\t\"%s\"\r\n", shader->shader ); + fprintf( f, "\t\t\t*MAP_CLASS\t\"Bitmap\"\r\n" ); + fprintf( f, "\t\t\t*MAP_SUBNO\t1\r\n" ); + fprintf( f, "\t\t\t*MAP_AMOUNT\t1.0\r\n" ); + fprintf( f, "\t\t\t*MAP_TYPE\tScreen\r\n" ); + if ( shadersAsBitmap ) { + fprintf( f, "\t\t\t*BITMAP\t\"%s\"\r\n", shader->shader ); + } + else{ + fprintf( f, "\t\t\t*BITMAP\t\"..\\%s\"\r\n", filename ); + } + fprintf( f, "\t\t\t*BITMAP_FILTER\tPyramidal\r\n" ); + fprintf( f, "\t\t}\r\n" ); + + fprintf( f, "\t}\r\n" ); +} +static void ConvertLightmap( FILE *f, const char *base, int lightmapNum ){ + /* print shader info */ + fprintf( f, "\t*MATERIAL\t%d\t{\r\n", lightmapNum ); + fprintf( f, "\t\t*MATERIAL_NAME\t\"lm_%04d\"\r\n", lightmapNum ); + fprintf( f, "\t\t*MATERIAL_CLASS\t\"Standard\"\r\n" ); + fprintf( f, "\t\t*MATERIAL_DIFFUSE\t1\t1\t1\r\n" ); + fprintf( f, "\t\t*MATERIAL_SHADING Phong\r\n" ); + + /* print map info */ + if ( lightmapNum >= 0 ) { + fprintf( f, "\t\t*MAP_DIFFUSE\t{\r\n" ); + fprintf( f, "\t\t\t*MAP_NAME\t\"lm_%04d\"\r\n", lightmapNum ); + fprintf( f, "\t\t\t*MAP_CLASS\t\"Bitmap\"\r\n" ); + fprintf( f, "\t\t\t*MAP_SUBNO\t1\r\n" ); + fprintf( f, "\t\t\t*MAP_AMOUNT\t1.0\r\n" ); + fprintf( f, "\t\t\t*MAP_TYPE\tScreen\r\n" ); + fprintf( f, "\t\t\t*BITMAP\t\"%s\\lm_%04d.tga\"\r\n", base, lightmapNum ); + fprintf( f, "\t\t\t*BITMAP_FILTER\tPyramidal\r\n" ); + fprintf( f, "\t\t}\r\n" ); + } + + fprintf( f, "\t}\r\n" ); +} + + + +/* + ConvertBSPToASE() + exports an 3d studio ase file from the bsp + */ + +int ConvertBSPToASE( char *bspName ){ + int i, modelNum; + FILE *f; + bspShader_t *shader; + bspModel_t *model; + entity_t *e; + vec3_t origin; + const char *key; + char name[ 1024 ], base[ 1024 ], dirname[ 1024 ]; + + + /* note it */ + Sys_Printf( "--- Convert BSP to ASE ---\n" ); + + /* create the ase filename from the bsp name */ + strcpy( dirname, bspName ); + StripExtension( dirname ); + strcpy( name, bspName ); + StripExtension( name ); + strcat( name, ".ase" ); + Sys_Printf( "writing %s\n", name ); + + ExtractFileBase( bspName, base ); + + /* open it */ + f = fopen( name, "wb" ); + if ( f == NULL ) { + Error( "Open failed on %s\n", name ); + } + + /* print header */ + fprintf( f, "*3DSMAX_ASCIIEXPORT\t200\r\n" ); + fprintf( f, "*COMMENT\t\"Generated by Q3Map2 (ydnar) -convert -format ase\"\r\n" ); + fprintf( f, "*SCENE\t{\r\n" ); + fprintf( f, "\t*SCENE_FILENAME\t\"%s.bsp\"\r\n", base ); + fprintf( f, "\t*SCENE_FIRSTFRAME\t0\r\n" ); + fprintf( f, "\t*SCENE_LASTFRAME\t100\r\n" ); + fprintf( f, "\t*SCENE_FRAMESPEED\t30\r\n" ); + fprintf( f, "\t*SCENE_TICKSPERFRAME\t160\r\n" ); + fprintf( f, "\t*SCENE_BACKGROUND_STATIC\t0.0000\t0.0000\t0.0000\r\n" ); + fprintf( f, "\t*SCENE_AMBIENT_STATIC\t0.0000\t0.0000\t0.0000\r\n" ); + fprintf( f, "}\r\n" ); + + /* print materials */ + fprintf( f, "*MATERIAL_LIST\t{\r\n" ); + if ( lightmapsAsTexcoord ) { + int lightmapCount; + for ( lightmapCount = 0; lightmapCount < numBSPLightmaps; lightmapCount++ ) + ; + for ( ; ; lightmapCount++ ) + { + char buf[1024]; + FILE *tmp; + snprintf( buf, sizeof( buf ), "%s/lm_%04d.tga", dirname, lightmapCount ); + buf[sizeof( buf ) - 1] = 0; + tmp = fopen( buf, "rb" ); + if ( !tmp ) { + break; + } + fclose( tmp ); + } + fprintf( f, "\t*MATERIAL_COUNT\t%d\r\n", lightmapCount ); + for ( i = 0; i < lightmapCount; i++ ) + ConvertLightmap( f, base, i ); + numLightmapsASE = lightmapCount; + } + else + { + fprintf( f, "\t*MATERIAL_COUNT\t%d\r\n", numBSPShaders ); + for ( i = 0; i < numBSPShaders; i++ ) + { + shader = &bspShaders[ i ]; + ConvertShader( f, shader, i ); + } + } + fprintf( f, "}\r\n" ); + + /* walk entity list */ + for ( i = 0; i < numEntities; i++ ) + { + /* get entity and model */ + e = &entities[ i ]; + if ( i == 0 ) { + modelNum = 0; + } + else + { + key = ValueForKey( e, "model" ); + if ( key[ 0 ] != '*' ) { + continue; + } + modelNum = atoi( key + 1 ); + } + model = &bspModels[ modelNum ]; + + /* get entity origin */ + key = ValueForKey( e, "origin" ); + if ( key[ 0 ] == '\0' ) { + VectorClear( origin ); + } + else{ + GetVectorForKey( e, "origin", origin ); + } + + /* convert model */ + ConvertModel( f, model, modelNum, origin ); + } + + /* close the file and return */ + fclose( f ); + + /* return to sender */ + return 0; +} diff --git a/tools/vmap/convert_bsp.c b/tools/vmap/convert_bsp.c new file mode 100644 index 0000000..d9c4512 --- /dev/null +++ b/tools/vmap/convert_bsp.c @@ -0,0 +1,273 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* dependencies */ +#include "vmap.h" + + + +/* + PseudoCompileBSP() + a stripped down ProcessModels + */ +void PseudoCompileBSP( qboolean need_tree, const char *BSPFilePath, const char *surfaceFilePath ){ + int models; + char modelValue[10]; + entity_t *entity; + face_t *faces; + tree_t *tree; + node_t *node; + brush_t *brush; + side_t *side; + int i; + + SetDrawSurfacesBuffer(); + mapDrawSurfs = safe_malloc( sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); + memset( mapDrawSurfs, 0, sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); + numMapDrawSurfs = 0; + + BeginBSPFile(); + models = 1; + for ( mapEntityNum = 0; mapEntityNum < numEntities; mapEntityNum++ ) + { + /* get entity */ + entity = &entities[ mapEntityNum ]; + if ( entity->brushes == NULL && entity->patches == NULL ) { + continue; + } + + if ( mapEntityNum != 0 ) { + sprintf( modelValue, "*%d", models++ ); + SetKeyValue( entity, "model", modelValue ); + } + + /* process the model */ + Sys_FPrintf( SYS_VRB, "############### model %i ###############\n", numBSPModels ); + BeginModel(); + + entity->firstDrawSurf = numMapDrawSurfs; + + ClearMetaTriangles(); + PatchMapDrawSurfs( entity ); + + if ( mapEntityNum == 0 && need_tree ) { + faces = MakeStructuralBSPFaceList( entities[0].brushes ); + tree = FaceBSP( faces ); + node = tree->headnode; + } + else + { + node = AllocNode(); + node->planenum = PLANENUM_LEAF; + tree = AllocTree(); + tree->headnode = node; + } + + /* a minimized ClipSidesIntoTree */ + for ( brush = entity->brushes; brush; brush = brush->next ) + { + /* walk the brush sides */ + for ( i = 0; i < brush->numsides; i++ ) + { + /* get side */ + side = &brush->sides[ i ]; + if ( side->winding == NULL ) { + continue; + } + /* shader? */ + if ( side->shaderInfo == NULL ) { + continue; + } + /* save this winding as a visible surface */ + DrawSurfaceForSide( entity, brush, side, side->winding ); + } + } + + if ( meta ) { + ClassifyEntitySurfaces( entity ); + MakeEntityDecals( entity ); + MakeEntityMetaTriangles( entity ); + SmoothMetaTriangles(); + MergeMetaTriangles(); + } + FilterDrawsurfsIntoTree( entity, tree ); + + FilterStructuralBrushesIntoTree( entity, tree ); + FilterDetailBrushesIntoTree( entity, tree ); + + EmitBrushes( entity->brushes, &entity->firstBrush, &entity->numBrushes ); + EndModel( entity, node ); + } + EndBSPFile( qfalse, BSPFilePath, surfaceFilePath ); +} + +/* + ConvertBSPMain() + main argument processing function for bsp conversion + */ + +int ConvertBSPMain( int argc, char **argv ){ + int i; + int ( *convertFunc )( char * ); + game_t *convertGame; + char ext[1024]; + char BSPFilePath [ 1024 ]; + char surfaceFilePath [ 1024 ]; + qboolean map_allowed, force_bsp, force_map; + + + /* set default */ + convertFunc = ConvertBSPToASE; + convertGame = NULL; + map_allowed = qfalse; + force_bsp = qfalse; + force_map = qfalse; + + /* arg checking */ + if ( argc < 1 ) { + Sys_Printf( "Usage: q3map -convert [-format ] [-shadersasbitmap|-lightmapsastexcoord|-deluxemapsastexcoord] [-readbsp|-readmap [-meta|-patchmeta]] [-v] \n" ); + return 0; + } + + /* process arguments */ + for ( i = 1; i < ( argc - 1 ); i++ ) + { + /* -format map|ase|... */ + if ( !strcmp( argv[ i ], "-format" ) ) { + i++; + if ( !Q_stricmp( argv[ i ], "ase" ) ) { + convertFunc = ConvertBSPToASE; + map_allowed = qfalse; + } + else if ( !Q_stricmp( argv[ i ], "obj" ) ) { + convertFunc = ConvertBSPToOBJ; + map_allowed = qfalse; + } + else if ( !Q_stricmp( argv[ i ], "map_bp" ) ) { + convertFunc = ConvertBSPToMap_BP; + map_allowed = qtrue; + } + else if ( !Q_stricmp( argv[ i ], "map" ) ) { + convertFunc = ConvertBSPToMap; + map_allowed = qtrue; + } + else + { + convertGame = GetGame( argv[ i ] ); + map_allowed = qfalse; + if ( convertGame == NULL ) { + Sys_Printf( "Unknown conversion format \"%s\". Defaulting to ASE.\n", argv[ i ] ); + } + } + } + else if ( !strcmp( argv[ i ], "-ne" ) ) { + normalEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Normal epsilon set to %f\n", normalEpsilon ); + } + else if ( !strcmp( argv[ i ], "-de" ) ) { + distanceEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Distance epsilon set to %f\n", distanceEpsilon ); + } + else if ( !strcmp( argv[ i ], "-shaderasbitmap" ) || !strcmp( argv[ i ], "-shadersasbitmap" ) ) { + shadersAsBitmap = qtrue; + } + else if ( !strcmp( argv[ i ], "-lightmapastexcoord" ) || !strcmp( argv[ i ], "-lightmapsastexcoord" ) ) { + lightmapsAsTexcoord = qtrue; + } + else if ( !strcmp( argv[ i ], "-deluxemapastexcoord" ) || !strcmp( argv[ i ], "-deluxemapsastexcoord" ) ) { + lightmapsAsTexcoord = qtrue; + deluxemap = qtrue; + } + else if ( !strcmp( argv[ i ], "-readbsp" ) ) { + force_bsp = qtrue; + } + else if ( !strcmp( argv[ i ], "-readmap" ) ) { + force_map = qtrue; + } + else if ( !strcmp( argv[ i ], "-meta" ) ) { + meta = qtrue; + } + else if ( !strcmp( argv[ i ], "-patchmeta" ) ) { + meta = qtrue; + patchMeta = qtrue; + } + } + + LoadShaderInfo(); + + /* clean up map name */ + strcpy( source, ExpandArg( argv[i] ) ); + ExtractFileExtension( source, ext ); + + if ( !map_allowed && !force_map ) { + force_bsp = qtrue; + } + + if ( force_map || ( !force_bsp && !Q_stricmp( ext, "map" ) && map_allowed ) ) { + if ( !map_allowed ) { + Sys_FPrintf( SYS_WRN, "WARNING: the requested conversion should not be done from .map files. Compile a .bsp first.\n" ); + } + StripExtension( source ); + DefaultExtension( source, ".map" ); + Sys_Printf( "Loading %s\n", source ); + LoadMapFile( source, qfalse, convertGame == NULL ); + sprintf( BSPFilePath, "%s.bsp", source ); + sprintf( surfaceFilePath, "%s.srf", source ); + PseudoCompileBSP( convertGame != NULL, BSPFilePath, surfaceFilePath ); + } + else + { + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + ParseEntities(); + } + + /* bsp format convert? */ + if ( convertGame != NULL ) { + /* set global game */ + game = convertGame; + + /* write bsp */ + StripExtension( source ); + DefaultExtension( source, "_c.bsp" ); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + /* return to sender */ + return 0; + } + + /* normal convert */ + return convertFunc( source ); +} diff --git a/tools/vmap/convert_map.c b/tools/vmap/convert_map.c new file mode 100644 index 0000000..0520ff2 --- /dev/null +++ b/tools/vmap/convert_map.c @@ -0,0 +1,886 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define CONVERT_MAP_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + ConvertBrush() + exports a map brush + */ + +#define SNAP_FLOAT_TO_INT 4 +#define SNAP_INT_TO_FLOAT ( 1.0 / SNAP_FLOAT_TO_INT ) + +typedef vec_t vec2_t[2]; + +static vec_t Det3x3( vec_t a00, vec_t a01, vec_t a02, + vec_t a10, vec_t a11, vec_t a12, + vec_t a20, vec_t a21, vec_t a22 ){ + return + a00 * ( a11 * a22 - a12 * a21 ) + - a01 * ( a10 * a22 - a12 * a20 ) + + a02 * ( a10 * a21 - a11 * a20 ); +} + +void GetBestSurfaceTriangleMatchForBrushside( side_t *buildSide, bspDrawVert_t *bestVert[3] ){ + bspDrawSurface_t *s; + int i; + int t; + vec_t best = 0; + vec_t thisarea; + vec3_t normdiff; + vec3_t v1v0, v2v0, norm; + bspDrawVert_t *vert[3]; + winding_t *polygon; + plane_t *buildPlane = &mapplanes[buildSide->planenum]; + int matches = 0; + + // first, start out with NULLs + bestVert[0] = bestVert[1] = bestVert[2] = NULL; + + // brute force through all surfaces + for ( s = bspDrawSurfaces; s != bspDrawSurfaces + numBSPDrawSurfaces; ++s ) + { + if ( s->surfaceType != MST_PLANAR && s->surfaceType != MST_TRIANGLE_SOUP ) { + continue; + } + if ( strcmp( buildSide->shaderInfo->shader, bspShaders[s->shaderNum].shader ) ) { + continue; + } + for ( t = 0; t + 3 <= s->numIndexes; t += 3 ) + { + vert[0] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 0]]; + vert[1] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 1]]; + vert[2] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 2]]; + if ( s->surfaceType == MST_PLANAR && VectorCompare( vert[0]->normal, vert[1]->normal ) && VectorCompare( vert[1]->normal, vert[2]->normal ) ) { + VectorSubtract( vert[0]->normal, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) { + continue; + } + VectorSubtract( vert[1]->normal, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) { + continue; + } + VectorSubtract( vert[2]->normal, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) { + continue; + } + } + else + { + // this is more prone to roundoff errors, but with embedded + // models, there is no better way + VectorSubtract( vert[1]->xyz, vert[0]->xyz, v1v0 ); + VectorSubtract( vert[2]->xyz, vert[0]->xyz, v2v0 ); + CrossProduct( v2v0, v1v0, norm ); + VectorNormalize( norm, norm ); + VectorSubtract( norm, buildPlane->normal, normdiff ); if ( VectorLength( normdiff ) >= normalEpsilon ) { + continue; + } + } + if ( abs( DotProduct( vert[0]->xyz, buildPlane->normal ) - buildPlane->dist ) >= distanceEpsilon ) { + continue; + } + if ( abs( DotProduct( vert[1]->xyz, buildPlane->normal ) - buildPlane->dist ) >= distanceEpsilon ) { + continue; + } + if ( abs( DotProduct( vert[2]->xyz, buildPlane->normal ) - buildPlane->dist ) >= distanceEpsilon ) { + continue; + } + // Okay. Correct surface type, correct shader, correct plane. Let's start with the business... + polygon = CopyWinding( buildSide->winding ); + for ( i = 0; i < 3; ++i ) + { + // 0: 1, 2 + // 1: 2, 0 + // 2; 0, 1 + vec3_t *v1 = &vert[( i + 1 ) % 3]->xyz; + vec3_t *v2 = &vert[( i + 2 ) % 3]->xyz; + vec3_t triNormal; + vec_t triDist; + vec3_t sideDirection; + // we now need to generate triNormal and triDist so that they represent the plane spanned by normal and (v2 - v1). + VectorSubtract( *v2, *v1, sideDirection ); + CrossProduct( sideDirection, buildPlane->normal, triNormal ); + triDist = DotProduct( *v1, triNormal ); + ChopWindingInPlace( &polygon, triNormal, triDist, distanceEpsilon ); + if ( !polygon ) { + goto exwinding; + } + } + thisarea = WindingArea( polygon ); + if ( thisarea > 0 ) { + ++matches; + } + if ( thisarea > best ) { + best = thisarea; + bestVert[0] = vert[0]; + bestVert[1] = vert[1]; + bestVert[2] = vert[2]; + } + FreeWinding( polygon ); +exwinding: + ; + } + } + //if(strncmp(buildSide->shaderInfo->shader, "textures/common/", 16)) + // fprintf(stderr, "brushside with %s: %d matches (%f area)\n", buildSide->shaderInfo->shader, matches, best); +} + +#define FRAC( x ) ( ( x ) - floor( x ) ) +static void ConvertOriginBrush( FILE *f, int num, vec3_t origin, qboolean brushPrimitives ){ + int originSize = 256; + + char pattern[6][7][3] = { + { "+++", "+-+", "-++", "- ", " + ", " - ", "- " }, + { "+++", "-++", "++-", "- ", " +", "+ ", " +" }, + { "+++", "++-", "+-+", " - ", " +", " - ", " +" }, + { "---", "+--", "-+-", "- ", " + ", " - ", "+ " }, + { "---", "--+", "+--", "- ", " +", "- ", " +" }, + { "---", "-+-", "--+", " - ", " +", " + ", " +" } + }; + int i; +#define S( a,b,c ) ( pattern[a][b][c] == '+' ? +1 : pattern[a][b][c] == '-' ? -1 : 0 ) + + /* start brush */ + fprintf( f, "\t// brush %d\n", num ); + fprintf( f, "\t{\n" ); + if ( brushPrimitives ) { + fprintf( f, "\tbrushDef\n" ); + fprintf( f, "\t{\n" ); + } + /* print brush side */ + /* ( 640 24 -224 ) ( 448 24 -224 ) ( 448 -232 -224 ) common/caulk 0 48 0 0.500000 0.500000 0 0 0 */ + + for ( i = 0; i < 6; ++i ) + { + if ( brushPrimitives ) { + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( ( %.8f %.8f %.8f ) ( %.8f %.8f %.8f ) ) %s %d 0 0\n", + origin[0] + 8 * S( i,0,0 ), origin[1] + 8 * S( i,0,1 ), origin[2] + 8 * S( i,0,2 ), + origin[0] + 8 * S( i,1,0 ), origin[1] + 8 * S( i,1,1 ), origin[2] + 8 * S( i,1,2 ), + origin[0] + 8 * S( i,2,0 ), origin[1] + 8 * S( i,2,1 ), origin[2] + 8 * S( i,2,2 ), + 1.0f / 16.0f, 0.0f, FRAC( ( S( i,5,0 ) * origin[0] + S( i,5,1 ) * origin[1] + S( i,5,2 ) * origin[2] ) / 16.0 + 0.5 ), + 0.0f, 1.0f / 16.0f, FRAC( ( S( i,6,0 ) * origin[0] + S( i,6,1 ) * origin[1] + S( i,6,2 ) * origin[2] ) / 16.0 + 0.5 ), + "common/origin", + 0 + ); + } + else + { + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) %s %.8f %.8f %.8f %.8f %.8f %d 0 0\n", + origin[0] + 8 * S( i,0,0 ), origin[1] + 8 * S( i,0,1 ), origin[2] + 8 * S( i,0,2 ), + origin[0] + 8 * S( i,1,0 ), origin[1] + 8 * S( i,1,1 ), origin[2] + 8 * S( i,1,2 ), + origin[0] + 8 * S( i,2,0 ), origin[1] + 8 * S( i,2,1 ), origin[2] + 8 * S( i,2,2 ), + "common/origin", + FRAC( ( S( i,3,0 ) * origin[0] + S( i,3,1 ) * origin[1] + S( i,3,2 ) * origin[2] ) / 16.0 + 0.5 ) * originSize, + FRAC( ( S( i,4,0 ) * origin[0] + S( i,4,1 ) * origin[1] + S( i,4,2 ) * origin[2] ) / 16.0 + 0.5 ) * originSize, + 0.0f, 16.0 / originSize, 16.0 / originSize, + 0 + ); + } + } +#undef S + + /* end brush */ + if ( brushPrimitives ) { + fprintf( f, "\t}\n" ); + } + fprintf( f, "\t}\n\n" ); +} + +static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin, qboolean brushPrimitives ){ + int i, j; + bspBrushSide_t *side; + side_t *buildSide; + bspShader_t *shader; + char *texture; + plane_t *buildPlane; + vec3_t pts[ 3 ]; + bspDrawVert_t *vert[3]; + + + /* start brush */ + fprintf( f, "\t// brush %d\n", num ); + fprintf( f, "\t{\n" ); + if ( brushPrimitives ) { + fprintf( f, "\tbrushDef\n" ); + fprintf( f, "\t{\n" ); + } + + /* clear out build brush */ + for ( i = 0; i < buildBrush->numsides; i++ ) + { + buildSide = &buildBrush->sides[ i ]; + if ( buildSide->winding != NULL ) { + FreeWinding( buildSide->winding ); + buildSide->winding = NULL; + } + } + buildBrush->numsides = 0; + + /* iterate through bsp brush sides */ + for ( i = 0; i < brush->numSides; i++ ) + { + /* get side */ + side = &bspBrushSides[ brush->firstSide + i ]; + + /* get shader */ + if ( side->shaderNum < 0 || side->shaderNum >= numBSPShaders ) { + continue; + } + shader = &bspShaders[ side->shaderNum ]; + //if( !Q_stricmp( shader->shader, "default" ) || !Q_stricmp( shader->shader, "noshader" ) ) + // continue; + + /* add build side */ + buildSide = &buildBrush->sides[ buildBrush->numsides ]; + buildBrush->numsides++; + + /* tag it */ + buildSide->shaderInfo = ShaderInfoForShader( shader->shader ); + buildSide->planenum = side->planeNum; + buildSide->winding = NULL; + } + + /* make brush windings */ + if ( !CreateBrushWindings( buildBrush ) ) { + Sys_Printf( "CreateBrushWindings failed\n" ); + return; + } + + /* iterate through build brush sides */ + for ( i = 0; i < buildBrush->numsides; i++ ) + { + /* get build side */ + buildSide = &buildBrush->sides[ i ]; + + /* get plane */ + buildPlane = &mapplanes[ buildSide->planenum ]; + + /* dummy check */ + if ( buildSide->shaderInfo == NULL || buildSide->winding == NULL ) { + continue; + } + + // st-texcoords -> texMat block + // start out with dummy + VectorSet( buildSide->texMat[0], 1 / 32.0, 0, 0 ); + VectorSet( buildSide->texMat[1], 0, 1 / 32.0, 0 ); + + // find surface for this side (by brute force) + // surface format: + // - meshverts point in pairs of three into verts + // - (triangles) + // - find the triangle that has most in common with our side + GetBestSurfaceTriangleMatchForBrushside( buildSide, vert ); + + /* get texture name */ + if ( !Q_strncasecmp( buildSide->shaderInfo->shader, "textures/", 9 ) ) { + texture = buildSide->shaderInfo->shader + 9; + } + else{ + texture = buildSide->shaderInfo->shader; + } + + /* get plane points and offset by origin */ + for ( j = 0; j < 3; j++ ) + { + VectorAdd( buildSide->winding->p[ j ], origin, pts[ j ] ); + //% pts[ j ][ 0 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 0 ] * SNAP_FLOAT_TO_INT + 0.5f ); + //% pts[ j ][ 1 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 1 ] * SNAP_FLOAT_TO_INT + 0.5f ); + //% pts[ j ][ 2 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 2 ] * SNAP_FLOAT_TO_INT + 0.5f ); + } + + if ( vert[0] && vert[1] && vert[2] ) { + if ( brushPrimitives ) { + int i; + vec3_t texX, texY; + vec2_t xyI, xyJ, xyK; + vec2_t stI, stJ, stK; + vec_t D, D0, D1, D2; + + ComputeAxisBase( buildPlane->normal, texX, texY ); + + xyI[0] = DotProduct( vert[0]->xyz, texX ); + xyI[1] = DotProduct( vert[0]->xyz, texY ); + xyJ[0] = DotProduct( vert[1]->xyz, texX ); + xyJ[1] = DotProduct( vert[1]->xyz, texY ); + xyK[0] = DotProduct( vert[2]->xyz, texX ); + xyK[1] = DotProduct( vert[2]->xyz, texY ); + stI[0] = vert[0]->st[0]; stI[1] = vert[0]->st[1]; + stJ[0] = vert[1]->st[0]; stJ[1] = vert[1]->st[1]; + stK[0] = vert[2]->st[0]; stK[1] = vert[2]->st[1]; + + // - solve linear equations: + // - (x, y) := xyz . (texX, texY) + // - st[i] = texMat[i][0]*x + texMat[i][1]*y + texMat[i][2] + // (for three vertices) + D = Det3x3( + xyI[0], xyI[1], 1, + xyJ[0], xyJ[1], 1, + xyK[0], xyK[1], 1 + ); + if ( D != 0 ) { + for ( i = 0; i < 2; ++i ) + { + D0 = Det3x3( + stI[i], xyI[1], 1, + stJ[i], xyJ[1], 1, + stK[i], xyK[1], 1 + ); + D1 = Det3x3( + xyI[0], stI[i], 1, + xyJ[0], stJ[i], 1, + xyK[0], stK[i], 1 + ); + D2 = Det3x3( + xyI[0], xyI[1], stI[i], + xyJ[0], xyJ[1], stJ[i], + xyK[0], xyK[1], stK[i] + ); + VectorSet( buildSide->texMat[i], D0 / D, D1 / D, D2 / D ); + } + } + else{ + fprintf( stderr, "degenerate triangle found when solving texMat equations for\n(%f %f %f) (%f %f %f) (%f %f %f)\n( %f %f %f )\n( %f %f %f ) -> ( %f %f )\n( %f %f %f ) -> ( %f %f )\n( %f %f %f ) -> ( %f %f )\n", + buildPlane->normal[0], buildPlane->normal[1], buildPlane->normal[2], + vert[0]->normal[0], vert[0]->normal[1], vert[0]->normal[2], + texX[0], texX[1], texX[2], texY[0], texY[1], texY[2], + vert[0]->xyz[0], vert[0]->xyz[1], vert[0]->xyz[2], xyI[0], xyI[1], + vert[1]->xyz[0], vert[1]->xyz[1], vert[1]->xyz[2], xyJ[0], xyJ[1], + vert[2]->xyz[0], vert[2]->xyz[1], vert[2]->xyz[2], xyK[0], xyK[1] + ); + } + + /* print brush side */ + /* ( 640 24 -224 ) ( 448 24 -224 ) ( 448 -232 -224 ) common/caulk 0 48 0 0.500000 0.500000 0 0 0 */ + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( ( %.8f %.8f %.8f ) ( %.8f %.8f %.8f ) ) %s %d 0 0\n", + pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ], + pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ], + pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ], + buildSide->texMat[0][0], buildSide->texMat[0][1], FRAC( buildSide->texMat[0][2] ), + buildSide->texMat[1][0], buildSide->texMat[1][1], FRAC( buildSide->texMat[1][2] ), + texture, + 0 + ); + } + else + { + // invert QuakeTextureVecs + int i; + vec3_t vecs[2]; + int sv, tv; + vec2_t stI, stJ, stK; + vec3_t sts[2]; + vec2_t shift, scale; + vec_t rotate; + vec_t D, D0, D1, D2; + + TextureAxisFromPlane( buildPlane, vecs[0], vecs[1] ); + if ( vecs[0][0] ) { + sv = 0; + } + else if ( vecs[0][1] ) { + sv = 1; + } + else{ + sv = 2; + } + if ( vecs[1][0] ) { + tv = 0; + } + else if ( vecs[1][1] ) { + tv = 1; + } + else{ + tv = 2; + } + + stI[0] = vert[0]->st[0] * buildSide->shaderInfo->shaderWidth; stI[1] = vert[0]->st[1] * buildSide->shaderInfo->shaderHeight; + stJ[0] = vert[1]->st[0] * buildSide->shaderInfo->shaderWidth; stJ[1] = vert[1]->st[1] * buildSide->shaderInfo->shaderHeight; + stK[0] = vert[2]->st[0] * buildSide->shaderInfo->shaderWidth; stK[1] = vert[2]->st[1] * buildSide->shaderInfo->shaderHeight; + + D = Det3x3( + vert[0]->xyz[sv], vert[0]->xyz[tv], 1, + vert[1]->xyz[sv], vert[1]->xyz[tv], 1, + vert[2]->xyz[sv], vert[2]->xyz[tv], 1 + ); + if ( D != 0 ) { + for ( i = 0; i < 2; ++i ) + { + D0 = Det3x3( + stI[i], vert[0]->xyz[tv], 1, + stJ[i], vert[1]->xyz[tv], 1, + stK[i], vert[2]->xyz[tv], 1 + ); + D1 = Det3x3( + vert[0]->xyz[sv], stI[i], 1, + vert[1]->xyz[sv], stJ[i], 1, + vert[2]->xyz[sv], stK[i], 1 + ); + D2 = Det3x3( + vert[0]->xyz[sv], vert[0]->xyz[tv], stI[i], + vert[1]->xyz[sv], vert[1]->xyz[tv], stJ[i], + vert[2]->xyz[sv], vert[2]->xyz[tv], stK[i] + ); + VectorSet( sts[i], D0 / D, D1 / D, D2 / D ); + } + } + else{ + fprintf( stderr, "degenerate triangle found when solving texDef equations\n" ); // FIXME add stuff here + + } + // now we must solve: + // // now we must invert: + // ang = rotate / 180 * Q_PI; + // sinv = sin(ang); + // cosv = cos(ang); + // ns = cosv * vecs[0][sv]; + // nt = sinv * vecs[0][sv]; + // vecsrotscaled[0][sv] = ns / scale[0]; + // vecsrotscaled[0][tv] = nt / scale[0]; + // ns = -sinv * vecs[1][tv]; + // nt = cosv * vecs[1][tv]; + // vecsrotscaled[1][sv] = ns / scale[1]; + // vecsrotscaled[1][tv] = nt / scale[1]; + scale[0] = 1.0 / sqrt( sts[0][0] * sts[0][0] + sts[0][1] * sts[0][1] ); + scale[1] = 1.0 / sqrt( sts[1][0] * sts[1][0] + sts[1][1] * sts[1][1] ); + rotate = atan2( sts[0][1] * vecs[0][sv] - sts[1][0] * vecs[1][tv], sts[0][0] * vecs[0][sv] + sts[1][1] * vecs[1][tv] ) * ( 180.0f / Q_PI ); + shift[0] = buildSide->shaderInfo->shaderWidth * FRAC( sts[0][2] / buildSide->shaderInfo->shaderWidth ); + shift[1] = buildSide->shaderInfo->shaderHeight * FRAC( sts[1][2] / buildSide->shaderInfo->shaderHeight ); + + /* print brush side */ + /* ( 640 24 -224 ) ( 448 24 -224 ) ( 448 -232 -224 ) common/caulk 0 48 0 0.500000 0.500000 0 0 0 */ + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) %s %.8f %.8f %.8f %.8f %.8f %d 0 0\n", + pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ], + pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ], + pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ], + texture, + shift[0], shift[1], rotate, scale[0], scale[1], + 0 + ); + } + } + else + { + vec3_t vecs[ 2 ]; + if ( strncmp( buildSide->shaderInfo->shader, "textures/common/", 16 ) ) { + if ( strcmp( buildSide->shaderInfo->shader, "noshader" ) ) { + if ( strcmp( buildSide->shaderInfo->shader, "default" ) ) { + fprintf( stderr, "no matching triangle for brushside using %s (hopefully nobody can see this side anyway)\n", buildSide->shaderInfo->shader ); + texture = "common/WTF"; + } + } + } + + MakeNormalVectors( buildPlane->normal, vecs[ 0 ], vecs[ 1 ] ); + VectorMA( vec3_origin, buildPlane->dist, buildPlane->normal, pts[ 0 ] ); + VectorMA( pts[ 0 ], 256.0f, vecs[ 0 ], pts[ 1 ] ); + VectorMA( pts[ 0 ], 256.0f, vecs[ 1 ], pts[ 2 ] ); + if ( brushPrimitives ) { + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( ( %.8f %.8f %.8f ) ( %.8f %.8f %.8f ) ) %s %d 0 0\n", + pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ], + pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ], + pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ], + 1.0f / 16.0f, 0.0f, 0.0f, + 0.0f, 1.0f / 16.0f, 0.0f, + texture, + 0 + ); + } + else + { + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) %s %.8f %.8f %.8f %.8f %.8f %d 0 0\n", + pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ], + pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ], + pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ], + texture, + 0.0f, 0.0f, 0.0f, 0.25f, 0.25f, + 0 + ); + } + } + } + + /* end brush */ + if ( brushPrimitives ) { + fprintf( f, "\t}\n" ); + } + fprintf( f, "\t}\n\n" ); +} +#undef FRAC + +#if 0 +/* iterate through the brush sides (ignore the first 6 bevel planes) */ +for ( i = 0; i < brush->numSides; i++ ) +{ + /* get side */ + side = &bspBrushSides[ brush->firstSide + i ]; + + /* get shader */ + if ( side->shaderNum < 0 || side->shaderNum >= numBSPShaders ) { + continue; + } + shader = &bspShaders[ side->shaderNum ]; + if ( !Q_stricmp( shader->shader, "default" ) || !Q_stricmp( shader->shader, "noshader" ) ) { + continue; + } + + /* get texture name */ + if ( !Q_strncasecmp( shader->shader, "textures/", 9 ) ) { + texture = shader->shader + 9; + } + else{ + texture = shader->shader; + } + + /* get plane */ + plane = &bspPlanes[ side->planeNum ]; + + /* make plane points */ + { + vec3_t vecs[ 2 ]; + + + MakeNormalVectors( plane->normal, vecs[ 0 ], vecs[ 1 ] ); + VectorMA( vec3_origin, plane->dist, plane->normal, pts[ 0 ] ); + VectorMA( pts[ 0 ], 256.0f, vecs[ 0 ], pts[ 1 ] ); + VectorMA( pts[ 0 ], 256.0f, vecs[ 1 ], pts[ 2 ] ); + } + + /* offset by origin */ + for ( j = 0; j < 3; j++ ) + VectorAdd( pts[ j ], origin, pts[ j ] ); + + /* print brush side */ + /* ( 640 24 -224 ) ( 448 24 -224 ) ( 448 -232 -224 ) common/caulk 0 48 0 0.500000 0.500000 0 0 0 */ + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) %s 0 0 0 0.5 0.5 0 0 0\n", + pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ], + pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ], + pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ], + texture ); +} +#endif + + + +/* + ConvertPatch() + converts a bsp patch to a map patch + + { + patchDef2 + { + base_wall/concrete + ( 9 3 0 0 0 ) + ( + ( ( 168 168 -192 0 2 ) ( 168 168 -64 0 1 ) ( 168 168 64 0 0 ) ... ) + ... + ) + } + } + + */ + +static void ConvertPatch( FILE *f, int num, bspDrawSurface_t *ds, vec3_t origin ){ + int x, y; + bspShader_t *shader; + char *texture; + bspDrawVert_t *dv; + vec3_t xyz; + qboolean fixed = qfalse; + int pw=ds->patchWidth, ph=ds->patchHeight; + + + /* only patches */ + if ( ds->surfaceType == MST_PATCHFIXED ) + fixed = qtrue; + else if ( ds->surfaceType != MST_PATCH ) { + return; + } + + /* get shader */ + if ( ds->shaderNum < 0 || ds->shaderNum >= numBSPShaders ) { + return; + } + shader = &bspShaders[ ds->shaderNum ]; + + /* get texture name */ + if ( !Q_strncasecmp( shader->shader, "textures/", 9 ) ) { + texture = shader->shader + 9; + } + else{ + texture = shader->shader; + } + + /* start patch */ + fprintf( f, "\t// patch %d\n", num ); + fprintf( f, "\t{\n" ); + fprintf( f, "\t\tpatchDef%i\n", fixed?3:2 ); + fprintf( f, "\t\t{\n" ); + fprintf( f, "\t\t\t%s\n", texture ); + if (fixed) + { + pw &= 0xffff; + ph &= 0xffff; + fprintf( f, "\t\t\t( %d %d %d %d 0 0 0 )\n", pw, ph, ds->patchWidth>>16, ds->patchHeight>>16 ); + } + else + fprintf( f, "\t\t\t( %d %d 0 0 0 )\n", pw, ph ); + fprintf( f, "\t\t\t(\n" ); + + /* iterate through the verts */ + for ( x = 0; x < pw; x++ ) + { + /* start row */ + fprintf( f, "\t\t\t\t(" ); + + /* iterate through the row */ + for ( y = 0; y < ph; y++ ) + { + /* get vert */ + dv = &bspDrawVerts[ ds->firstVert + ( y * pw ) + x ]; + + /* offset it */ + VectorAdd( origin, dv->xyz, xyz ); + + /* print vertex */ + fprintf( f, " ( %f %f %f %f %f )", xyz[ 0 ], xyz[ 1 ], xyz[ 2 ], dv->st[ 0 ], dv->st[ 1 ] ); + } + + /* end row */ + fprintf( f, " )\n" ); + } + + /* end patch */ + fprintf( f, "\t\t\t)\n" ); + fprintf( f, "\t\t}\n" ); + fprintf( f, "\t}\n\n" ); +} + + + +/* + ConvertModel() + exports a bsp model to a map file + */ + +static void ConvertModel( FILE *f, bspModel_t *model, int modelNum, vec3_t origin, qboolean brushPrimitives ){ + int i, num; + bspBrush_t *brush; + bspDrawSurface_t *ds; + + + /* convert bsp planes to map planes */ + nummapplanes = numBSPPlanes; + AUTOEXPAND_BY_REALLOC( mapplanes, nummapplanes, allocatedmapplanes, 1024 ); + for ( i = 0; i < numBSPPlanes; i++ ) + { + VectorCopy( bspPlanes[ i ].normal, mapplanes[ i ].normal ); + mapplanes[ i ].dist = bspPlanes[ i ].dist; + mapplanes[ i ].type = PlaneTypeForNormal( mapplanes[ i ].normal ); + mapplanes[ i ].hash_chain = 0; + } + + /* allocate a build brush */ + buildBrush = AllocBrush( 512 ); + buildBrush->entityNum = 0; + buildBrush->original = buildBrush; + + if ( origin[0] != 0 || origin[1] != 0 || origin[2] != 0 ) { + ConvertOriginBrush( f, -1, origin, brushPrimitives ); + } + + /* go through each brush in the model */ + for ( i = 0; i < model->numBSPBrushes; i++ ) + { + num = i + model->firstBSPBrush; + brush = &bspBrushes[ num ]; + ConvertBrush( f, num, brush, origin, brushPrimitives ); + } + + /* free the build brush */ + free( buildBrush ); + + /* go through each drawsurf in the model */ + for ( i = 0; i < model->numBSPSurfaces; i++ ) + { + num = i + model->firstBSPSurface; + ds = &bspDrawSurfaces[ num ]; + + /* we only love patches */ + if ( ds->surfaceType == MST_PATCH ) { + ConvertPatch( f, num, ds, origin ); + } + } +} + + + +/* + ConvertEPairs() + exports entity key/value pairs to a map file + */ + +static void ConvertEPairs( FILE *f, entity_t *e, qboolean skip_origin ){ + epair_t *ep; + + + /* walk epairs */ + for ( ep = e->epairs; ep != NULL; ep = ep->next ) + { + /* ignore empty keys/values */ + if ( ep->key[ 0 ] == '\0' || ep->value[ 0 ] == '\0' ) { + continue; + } + + /* ignore model keys with * prefixed values */ + if ( !Q_stricmp( ep->key, "model" ) && ep->value[ 0 ] == '*' ) { + continue; + } + + /* ignore origin keys if skip_origin is set */ + if ( skip_origin && !Q_stricmp( ep->key, "origin" ) ) { + continue; + } + + /* emit the epair */ + fprintf( f, "\t\"%s\" \"%s\"\n", ep->key, ep->value ); + } +} + + + +/* + ConvertBSPToMap() + exports an quake map file from the bsp + */ + +int ConvertBSPToMap_Ext( char *bspName, qboolean brushPrimitives ){ + int i, modelNum; + FILE *f; + bspModel_t *model; + entity_t *e; + vec3_t origin; + const char *value; + char name[ 1024 ], base[ 1024 ]; + + + /* note it */ + Sys_Printf( "--- Convert BSP to MAP ---\n" ); + + /* create the bsp filename from the bsp name */ + strcpy( name, bspName ); + StripExtension( name ); + strcat( name, "_converted.map" ); + Sys_Printf( "writing %s\n", name ); + + ExtractFileBase( bspName, base ); + strcat( base, ".bsp" ); + + /* open it */ + f = fopen( name, "wb" ); + if ( f == NULL ) { + Error( "Open failed on %s\n", name ); + } + + /* print header */ + fprintf( f, "// Generated by Q3Map2 (ydnar) -convert -format map\n" ); + + /* walk entity list */ + for ( i = 0; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + + /* start entity */ + fprintf( f, "// entity %d\n", i ); + fprintf( f, "{\n" ); + + /* get model num */ + if ( i == 0 ) { + modelNum = 0; + } + else + { + value = ValueForKey( e, "model" ); + if ( value[ 0 ] == '*' ) { + modelNum = atoi( value + 1 ); + } + else{ + modelNum = -1; + } + } + + /* export keys */ + ConvertEPairs( f, e, modelNum >= 0 ); + fprintf( f, "\n" ); + + /* only handle bsp models */ + if ( modelNum >= 0 ) { + /* get model */ + model = &bspModels[ modelNum ]; + + /* get entity origin */ + value = ValueForKey( e, "origin" ); + if ( value[ 0 ] == '\0' ) { + VectorClear( origin ); + } + else{ + GetVectorForKey( e, "origin", origin ); + } + + /* convert model */ + ConvertModel( f, model, modelNum, origin, brushPrimitives ); + } + + /* end entity */ + fprintf( f, "}\n\n" ); + } + + /* close the file and return */ + fclose( f ); + + /* return to sender */ + return 0; +} + +int ConvertBSPToMap( char *bspName ){ + return ConvertBSPToMap_Ext( bspName, qfalse ); +} + +int ConvertBSPToMap_BP( char *bspName ){ + return ConvertBSPToMap_Ext( bspName, qtrue ); +} diff --git a/tools/vmap/convert_obj.c b/tools/vmap/convert_obj.c new file mode 100644 index 0000000..29124f8 --- /dev/null +++ b/tools/vmap/convert_obj.c @@ -0,0 +1,326 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define CONVERT_ASE_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + ConvertSurface() + converts a bsp drawsurface to an obj chunk + */ + +int firstLightmap = 0; +int lastLightmap = -1; +static void ConvertLightmapToMTL( FILE *f, const char *base, int lightmapNum ); + +int objVertexCount = 0; +int objLastShaderNum = -1; +static void ConvertSurfaceToOBJ( FILE *f, bspModel_t *model, int modelNum, bspDrawSurface_t *ds, int surfaceNum, vec3_t origin ){ + int i, v, a, b, c; + bspDrawVert_t *dv; + + /* ignore patches for now */ + if ( ds->surfaceType != MST_PLANAR && ds->surfaceType != MST_TRIANGLE_SOUP ) { + return; + } + + fprintf( f, "g mat%dmodel%dsurf%d\r\n", ds->shaderNum, modelNum, surfaceNum ); + switch ( ds->surfaceType ) + { + case MST_PLANAR: + fprintf( f, "# SURFACETYPE MST_PLANAR\r\n" ); + break; + case MST_TRIANGLE_SOUP: + fprintf( f, "# SURFACETYPE MST_TRIANGLE_SOUP\r\n" ); + break; + } + + /* export shader */ + if ( lightmapsAsTexcoord ) { + if ( objLastShaderNum != ds->lightmapNum[0] ) { + fprintf( f, "usemtl lm_%04d\r\n", ds->lightmapNum[0] + deluxemap ); + objLastShaderNum = ds->lightmapNum[0] + deluxemap; + } + if ( ds->lightmapNum[0] + (int)deluxemap < firstLightmap ) { + Sys_FPrintf( SYS_WRN, "WARNING: lightmap %d out of range (exporting anyway)\n", ds->lightmapNum[0] + deluxemap ); + firstLightmap = ds->lightmapNum[0] + deluxemap; + } + if ( ds->lightmapNum[0] > lastLightmap ) { + Sys_FPrintf( SYS_WRN, "WARNING: lightmap %d out of range (exporting anyway)\n", ds->lightmapNum[0] + deluxemap ); + lastLightmap = ds->lightmapNum[0] + deluxemap; + } + } + else + { + if ( objLastShaderNum != ds->shaderNum ) { + fprintf( f, "usemtl %s\r\n", bspShaders[ds->shaderNum].shader ); + objLastShaderNum = ds->shaderNum; + } + } + + /* export vertex */ + for ( i = 0; i < ds->numVerts; i++ ) + { + v = i + ds->firstVert; + dv = &bspDrawVerts[ v ]; + fprintf( f, "# vertex %d\r\n", i + objVertexCount + 1 ); + fprintf( f, "v %f %f %f\r\n", dv->xyz[ 0 ], dv->xyz[ 1 ], dv->xyz[ 2 ] ); + fprintf( f, "vn %f %f %f\r\n", dv->normal[ 0 ], dv->normal[ 1 ], dv->normal[ 2 ] ); + if ( lightmapsAsTexcoord ) { + fprintf( f, "vt %f %f\r\n", dv->lightmap[0][0], 1.0 - dv->lightmap[0][1] ); + } + else{ + fprintf( f, "vt %f %f\r\n", dv->st[ 0 ], 1.0 - dv->st[ 1 ] ); + } + } + + /* export faces */ + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + a = bspDrawIndexes[ i + ds->firstIndex ]; + c = bspDrawIndexes[ i + ds->firstIndex + 1 ]; + b = bspDrawIndexes[ i + ds->firstIndex + 2 ]; + fprintf( f, "f %d/%d/%d %d/%d/%d %d/%d/%d\r\n", + a + objVertexCount + 1, a + objVertexCount + 1, a + objVertexCount + 1, + b + objVertexCount + 1, b + objVertexCount + 1, b + objVertexCount + 1, + c + objVertexCount + 1, c + objVertexCount + 1, c + objVertexCount + 1 + ); + } + + objVertexCount += ds->numVerts; +} + + + +/* + ConvertModel() + exports a bsp model to an ase chunk + */ + +static void ConvertModelToOBJ( FILE *f, bspModel_t *model, int modelNum, vec3_t origin ){ + int i, s; + bspDrawSurface_t *ds; + + + /* go through each drawsurf in the model */ + for ( i = 0; i < model->numBSPSurfaces; i++ ) + { + s = i + model->firstBSPSurface; + ds = &bspDrawSurfaces[ s ]; + ConvertSurfaceToOBJ( f, model, modelNum, ds, s, origin ); + } +} + + + +/* + ConvertShader() + exports a bsp shader to an ase chunk + */ + +static void ConvertShaderToMTL( FILE *f, bspShader_t *shader, int shaderNum ){ + shaderInfo_t *si; + char filename[ 1024 ]; + + + /* get shader */ + si = ShaderInfoForShader( shader->shader ); + if ( si == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: NULL shader in BSP\n" ); + return; + } + + /* set bitmap filename */ + if ( si->shaderImage->filename[ 0 ] != '*' ) { + strcpy( filename, si->shaderImage->filename ); + } + else{ + sprintf( filename, "%s.tga", si->shader ); + } + + /* blender hates this, so let's not do it + for( c = filename; *c != '\0'; c++ ) + if( *c == '/' ) + *c = '\\'; + */ + + /* print shader info */ + fprintf( f, "newmtl %s\r\n", shader->shader ); + fprintf( f, "Kd %f %f %f\r\n", si->color[ 0 ], si->color[ 1 ], si->color[ 2 ] ); + if ( shadersAsBitmap ) { + fprintf( f, "map_Kd %s\r\n", shader->shader ); + } + else{ + /* blender hates this, so let's not do it + fprintf( f, "map_Kd ..\\%s\r\n", filename ); + */ + fprintf( f, "map_Kd ../%s\r\n", filename ); + } +} + +static void ConvertLightmapToMTL( FILE *f, const char *base, int lightmapNum ){ + /* print shader info */ + fprintf( f, "newmtl lm_%04d\r\n", lightmapNum ); + if ( lightmapNum >= 0 ) { + /* blender hates this, so let's not do it + fprintf( f, "map_Kd %s\\lm_%04d.tga\r\n", base, lightmapNum ); + */ + fprintf( f, "map_Kd %s/lm_%04d.tga\r\n", base, lightmapNum ); + } +} + + + +/* + ConvertBSPToASE() + exports an 3d studio ase file from the bsp + */ + +int ConvertBSPToOBJ( char *bspName ){ + int i, modelNum; + FILE *f, *fmtl; + bspShader_t *shader; + bspModel_t *model; + entity_t *e; + vec3_t origin; + const char *key; + char name[ 1024 ], base[ 1024 ], mtlname[ 1024 ], dirname[ 1024 ]; + + + /* note it */ + Sys_Printf( "--- Convert BSP to OBJ ---\n" ); + + /* create the ase filename from the bsp name */ + strcpy( dirname, bspName ); + StripExtension( dirname ); + strcpy( name, bspName ); + StripExtension( name ); + strcat( name, ".obj" ); + Sys_Printf( "writing %s\n", name ); + strcpy( mtlname, bspName ); + StripExtension( mtlname ); + strcat( mtlname, ".mtl" ); + Sys_Printf( "writing %s\n", mtlname ); + + ExtractFileBase( bspName, base ); + + /* open it */ + f = fopen( name, "wb" ); + if ( f == NULL ) { + Error( "Open failed on %s\n", name ); + } + fmtl = fopen( mtlname, "wb" ); + if ( fmtl == NULL ) { + Error( "Open failed on %s\n", mtlname ); + } + + /* print header */ + fprintf( f, "o %s\r\n", base ); + fprintf( f, "# Generated by Q3Map2 (ydnar) -convert -format obj\r\n" ); + fprintf( f, "mtllib %s.mtl\r\n", base ); + + fprintf( fmtl, "# Generated by Q3Map2 (ydnar) -convert -format obj\r\n" ); + if ( lightmapsAsTexcoord ) { + int lightmapCount; + for ( lightmapCount = 0; lightmapCount < numBSPLightmaps; lightmapCount++ ) + ; + for ( ; ; lightmapCount++ ) + { + char buf[1024]; + FILE *tmp; + snprintf( buf, sizeof( buf ), "%s/lm_%04d.tga", dirname, lightmapCount ); + buf[sizeof( buf ) - 1] = 0; + tmp = fopen( buf, "rb" ); + if ( !tmp ) { + break; + } + fclose( tmp ); + } + lastLightmap = lightmapCount - 1; + } + else + { + for ( i = 0; i < numBSPShaders; i++ ) + { + shader = &bspShaders[ i ]; + ConvertShaderToMTL( fmtl, shader, i ); + } + } + + /* walk entity list */ + for ( i = 0; i < numEntities; i++ ) + { + /* get entity and model */ + e = &entities[ i ]; + if ( i == 0 ) { + modelNum = 0; + } + else + { + key = ValueForKey( e, "model" ); + if ( key[ 0 ] != '*' ) { + continue; + } + modelNum = atoi( key + 1 ); + } + model = &bspModels[ modelNum ]; + + /* get entity origin */ + key = ValueForKey( e, "origin" ); + if ( key[ 0 ] == '\0' ) { + VectorClear( origin ); + } + else{ + GetVectorForKey( e, "origin", origin ); + } + + /* convert model */ + ConvertModelToOBJ( f, model, modelNum, origin ); + } + + if ( lightmapsAsTexcoord ) { + for ( i = firstLightmap; i <= lastLightmap; i++ ) + ConvertLightmapToMTL( fmtl, base, i ); + } + + /* close the file and return */ + fclose( f ); + fclose( fmtl ); + + /* return to sender */ + return 0; +} diff --git a/tools/vmap/decals.c b/tools/vmap/decals.c new file mode 100644 index 0000000..4c3cfb6 --- /dev/null +++ b/tools/vmap/decals.c @@ -0,0 +1,902 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define DECALS_C + + + +/* dependencies */ +#include "vmap.h" + + + +#define MAX_PROJECTORS 1024 + +typedef struct decalProjector_s +{ + shaderInfo_t *si; + vec3_t mins, maxs; + vec3_t center; + float radius, radius2; + int numPlanes; /* either 5 or 6, for quad or triangle projectors */ + vec4_t planes[ 6 ]; + vec4_t texMat[ 2 ]; +} +decalProjector_t; + +static int numProjectors = 0; +static decalProjector_t projectors[ MAX_PROJECTORS ]; + +static int numDecalSurfaces = 0; + +static vec3_t entityOrigin; + + + +/* + DVectorNormalize() + normalizes a vector, returns the length, operates using doubles + */ + +typedef double dvec_t; +typedef dvec_t dvec3_t[ 3 ]; + +dvec_t DVectorNormalize( dvec3_t in, dvec3_t out ){ + dvec_t len, ilen; + + + len = (dvec_t) sqrt( in[ 0 ] * in[ 0 ] + in[ 1 ] * in[ 1 ] + in[ 2 ] * in[ 2 ] ); + if ( len == 0.0 ) { + VectorClear( out ); + return 0.0; + } + + ilen = 1.0 / len; + out[ 0 ] = in[ 0 ] * ilen; + out[ 1 ] = in[ 1 ] * ilen; + out[ 2 ] = in[ 2 ] * ilen; + + return len; +} + + + +/* + MakeTextureMatrix() + generates a texture projection matrix for a triangle + returns qfalse if a texture matrix cannot be created + */ + +#define Vector2Subtract( a,b,c ) ( ( c )[ 0 ] = ( a )[ 0 ] - ( b )[ 0 ], ( c )[ 1 ] = ( a )[ 1 ] - ( b )[ 1 ] ) + +static qboolean MakeTextureMatrix( decalProjector_t *dp, vec4_t projection, bspDrawVert_t *a, bspDrawVert_t *b, bspDrawVert_t *c ){ + int i, j; + double bb, s, t, d; + dvec3_t pa, pb, pc; + dvec3_t bary, xyz; + dvec3_t vecs[ 3 ], axis[ 3 ], lengths; + + + /* project triangle onto plane of projection */ + d = DotProduct( a->xyz, projection ) - projection[ 3 ]; + VectorMA( a->xyz, -d, projection, pa ); + d = DotProduct( b->xyz, projection ) - projection[ 3 ]; + VectorMA( b->xyz, -d, projection, pb ); + d = DotProduct( c->xyz, projection ) - projection[ 3 ]; + VectorMA( c->xyz, -d, projection, pc ); + + /* two methods */ + #if 1 + { + /* old code */ + + /* calculate barycentric basis for the triangle */ + bb = ( b->st[ 0 ] - a->st[ 0 ] ) * ( c->st[ 1 ] - a->st[ 1 ] ) - ( c->st[ 0 ] - a->st[ 0 ] ) * ( b->st[ 1 ] - a->st[ 1 ] ); + if ( fabs( bb ) < 0.00000001 ) { + return qfalse; + } + + /* calculate texture origin */ + #if 0 + s = 0.0; + t = 0.0; + bary[ 0 ] = ( ( b->st[ 0 ] - s ) * ( c->st[ 1 ] - t ) - ( c->st[ 0 ] - s ) * ( b->st[ 1 ] - t ) ) / bb; + bary[ 1 ] = ( ( c->st[ 0 ] - s ) * ( a->st[ 1 ] - t ) - ( a->st[ 0 ] - s ) * ( c->st[ 1 ] - t ) ) / bb; + bary[ 2 ] = ( ( a->st[ 0 ] - s ) * ( b->st[ 1 ] - t ) - ( b->st[ 0 ] - s ) * ( a->st[ 1 ] - t ) ) / bb; + + origin[ 0 ] = bary[ 0 ] * pa[ 0 ] + bary[ 1 ] * pb[ 0 ] + bary[ 2 ] * pc[ 0 ]; + origin[ 1 ] = bary[ 0 ] * pa[ 1 ] + bary[ 1 ] * pb[ 1 ] + bary[ 2 ] * pc[ 1 ]; + origin[ 2 ] = bary[ 0 ] * pa[ 2 ] + bary[ 1 ] * pb[ 2 ] + bary[ 2 ] * pc[ 2 ]; + #endif + + /* calculate s vector */ + s = a->st[ 0 ] + 1.0; + t = a->st[ 1 ] + 0.0; + bary[ 0 ] = ( ( b->st[ 0 ] - s ) * ( c->st[ 1 ] - t ) - ( c->st[ 0 ] - s ) * ( b->st[ 1 ] - t ) ) / bb; + bary[ 1 ] = ( ( c->st[ 0 ] - s ) * ( a->st[ 1 ] - t ) - ( a->st[ 0 ] - s ) * ( c->st[ 1 ] - t ) ) / bb; + bary[ 2 ] = ( ( a->st[ 0 ] - s ) * ( b->st[ 1 ] - t ) - ( b->st[ 0 ] - s ) * ( a->st[ 1 ] - t ) ) / bb; + + xyz[ 0 ] = bary[ 0 ] * pa[ 0 ] + bary[ 1 ] * pb[ 0 ] + bary[ 2 ] * pc[ 0 ]; + xyz[ 1 ] = bary[ 0 ] * pa[ 1 ] + bary[ 1 ] * pb[ 1 ] + bary[ 2 ] * pc[ 1 ]; + xyz[ 2 ] = bary[ 0 ] * pa[ 2 ] + bary[ 1 ] * pb[ 2 ] + bary[ 2 ] * pc[ 2 ]; + + //% VectorSubtract( xyz, origin, vecs[ 0 ] ); + VectorSubtract( xyz, pa, vecs[ 0 ] ); + + /* calculate t vector */ + s = a->st[ 0 ] + 0.0; + t = a->st[ 1 ] + 1.0; + bary[ 0 ] = ( ( b->st[ 0 ] - s ) * ( c->st[ 1 ] - t ) - ( c->st[ 0 ] - s ) * ( b->st[ 1 ] - t ) ) / bb; + bary[ 1 ] = ( ( c->st[ 0 ] - s ) * ( a->st[ 1 ] - t ) - ( a->st[ 0 ] - s ) * ( c->st[ 1 ] - t ) ) / bb; + bary[ 2 ] = ( ( a->st[ 0 ] - s ) * ( b->st[ 1 ] - t ) - ( b->st[ 0 ] - s ) * ( a->st[ 1 ] - t ) ) / bb; + + xyz[ 0 ] = bary[ 0 ] * pa[ 0 ] + bary[ 1 ] * pb[ 0 ] + bary[ 2 ] * pc[ 0 ]; + xyz[ 1 ] = bary[ 0 ] * pa[ 1 ] + bary[ 1 ] * pb[ 1 ] + bary[ 2 ] * pc[ 1 ]; + xyz[ 2 ] = bary[ 0 ] * pa[ 2 ] + bary[ 1 ] * pb[ 2 ] + bary[ 2 ] * pc[ 2 ]; + + //% VectorSubtract( xyz, origin, vecs[ 1 ] ); + VectorSubtract( xyz, pa, vecs[ 1 ] ); + + /* calcuate r vector */ + VectorScale( projection, -1.0, vecs[ 2 ] ); + + /* calculate transform axis */ + for ( i = 0; i < 3; i++ ) + lengths[ i ] = DVectorNormalize( vecs[ i ], axis[ i ] ); + for ( i = 0; i < 2; i++ ) + for ( j = 0; j < 3; j++ ) + dp->texMat[ i ][ j ] = lengths[ i ] > 0.0 ? ( axis[ i ][ j ] / lengths[ i ] ) : 0.0; + //% dp->texMat[ i ][ j ] = fabs( vecs[ i ][ j ] ) > 0.0 ? (1.0 / vecs[ i ][ j ]) : 0.0; + //% dp->texMat[ i ][ j ] = axis[ i ][ j ] > 0.0 ? (1.0 / axis[ i ][ j ]) : 0.0; + + /* calculalate translation component */ + dp->texMat[ 0 ][ 3 ] = a->st[ 0 ] - DotProduct( a->xyz, dp->texMat[ 0 ] ); + dp->texMat[ 1 ][ 3 ] = a->st[ 1 ] - DotProduct( a->xyz, dp->texMat[ 1 ] ); + } + #else + { + int k; + dvec3_t origin, deltas[ 3 ]; + double texDeltas[ 3 ][ 2 ]; + double delta, texDelta; + + + /* new code */ + + /* calculate deltas */ + VectorSubtract( pa, pb, deltas[ 0 ] ); + VectorSubtract( pa, pc, deltas[ 1 ] ); + VectorSubtract( pb, pc, deltas[ 2 ] ); + Vector2Subtract( a->st, b->st, texDeltas[ 0 ] ); + Vector2Subtract( a->st, c->st, texDeltas[ 1 ] ); + Vector2Subtract( b->st, c->st, texDeltas[ 2 ] ); + + /* walk st */ + for ( i = 0; i < 2; i++ ) + { + /* walk xyz */ + for ( j = 0; j < 3; j++ ) + { + /* clear deltas */ + delta = 0.0; + texDelta = 0.0; + + /* walk deltas */ + for ( k = 0; k < 3; k++ ) + { + if ( fabs( deltas[ k ][ j ] ) > delta && + fabs( texDeltas[ k ][ i ] ) > texDelta ) { + delta = deltas[ k ][ j ]; + texDelta = texDeltas[ k ][ i ]; + } + } + + /* set texture matrix component */ + if ( fabs( delta ) > 0.0 ) { + dp->texMat[ i ][ j ] = texDelta / delta; + } + else{ + dp->texMat[ i ][ j ] = 0.0; + } + } + + /* set translation component */ + dp->texMat[ i ][ 3 ] = a->st[ i ] - DotProduct( pa, dp->texMat[ i ] ); + } + } + #endif + + /* debug code */ + #if 1 + Sys_Printf( "Mat: [ %f %f %f %f ] [ %f %f %f %f ] Theta: %f (%f)\n", + dp->texMat[ 0 ][ 0 ], dp->texMat[ 0 ][ 1 ], dp->texMat[ 0 ][ 2 ], dp->texMat[ 0 ][ 3 ], + dp->texMat[ 1 ][ 0 ], dp->texMat[ 1 ][ 1 ], dp->texMat[ 1 ][ 2 ], dp->texMat[ 1 ][ 3 ], + RAD2DEG( acos( DotProduct( dp->texMat[ 0 ], dp->texMat[ 1 ] ) ) ), + RAD2DEG( acos( DotProduct( axis[ 0 ], axis[ 1 ] ) ) ) ); + + Sys_Printf( "XYZ: %f %f %f ST: %f %f ST(t): %f %f\n", + a->xyz[ 0 ], a->xyz[ 1 ], a->xyz[ 2 ], + a->st[ 0 ], a->st[ 1 ], + DotProduct( a->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ], DotProduct( a->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ] ); + #endif + + /* test texture matrix */ + s = DotProduct( a->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + t = DotProduct( a->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + if ( fabs( s - a->st[ 0 ] ) > 0.01 || fabs( t - a->st[ 1 ] ) > 0.01 ) { + Sys_Printf( "Bad texture matrix! (A) (%f, %f) != (%f, %f)\n", + s, t, a->st[ 0 ], a->st[ 1 ] ); + //% return qfalse; + } + s = DotProduct( b->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + t = DotProduct( b->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + if ( fabs( s - b->st[ 0 ] ) > 0.01 || fabs( t - b->st[ 1 ] ) > 0.01 ) { + Sys_Printf( "Bad texture matrix! (B) (%f, %f) != (%f, %f)\n", + s, t, b->st[ 0 ], b->st[ 1 ] ); + //% return qfalse; + } + s = DotProduct( c->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + t = DotProduct( c->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + if ( fabs( s - c->st[ 0 ] ) > 0.01 || fabs( t - c->st[ 1 ] ) > 0.01 ) { + Sys_Printf( "Bad texture matrix! (C) (%f, %f) != (%f, %f)\n", + s, t, c->st[ 0 ], c->st[ 1 ] ); + //% return qfalse; + } + + /* disco */ + return qtrue; +} + + + +/* + TransformDecalProjector() + transforms a decal projector + note: non-normalized axes will screw up the plane transform + */ + +static void TransformDecalProjector( decalProjector_t *in, vec3_t axis[ 3 ], vec3_t origin, decalProjector_t *out ){ + int i; + + + /* copy misc stuff */ + out->si = in->si; + out->numPlanes = in->numPlanes; + + /* translate bounding box and sphere (note: rotated projector bounding box will be invalid!) */ + VectorSubtract( in->mins, origin, out->mins ); + VectorSubtract( in->maxs, origin, out->maxs ); + VectorSubtract( in->center, origin, out->center ); + out->radius = in->radius; + out->radius2 = in->radius2; + + /* translate planes */ + for ( i = 0; i < in->numPlanes; i++ ) + { + out->planes[ i ][ 0 ] = DotProduct( in->planes[ i ], axis[ 0 ] ); + out->planes[ i ][ 1 ] = DotProduct( in->planes[ i ], axis[ 1 ] ); + out->planes[ i ][ 2 ] = DotProduct( in->planes[ i ], axis[ 2 ] ); + out->planes[ i ][ 3 ] = in->planes[ i ][ 3 ] - DotProduct( out->planes[ i ], origin ); + } + + /* translate texture matrix */ + for ( i = 0; i < 2; i++ ) + { + out->texMat[ i ][ 0 ] = DotProduct( in->texMat[ i ], axis[ 0 ] ); + out->texMat[ i ][ 1 ] = DotProduct( in->texMat[ i ], axis[ 1 ] ); + out->texMat[ i ][ 2 ] = DotProduct( in->texMat[ i ], axis[ 2 ] ); + out->texMat[ i ][ 3 ] = in->texMat[ i ][ 3 ] + DotProduct( out->texMat[ i ], origin ); + } +} + + + +/* + MakeDecalProjector() + creates a new decal projector from a triangle + */ + +static int MakeDecalProjector( shaderInfo_t *si, vec4_t projection, float distance, int numVerts, bspDrawVert_t **dv ){ + int i, j; + decalProjector_t *dp; + vec3_t xyz; + + + /* dummy check */ + if ( numVerts != 3 && numVerts != 4 ) { + return -1; + } + + /* limit check */ + if ( numProjectors >= MAX_PROJECTORS ) { + Sys_FPrintf( SYS_WRN, "WARNING: MAX_PROJECTORS (%d) exceeded, no more decal projectors available.\n", MAX_PROJECTORS ); + return -2; + } + + /* create a new projector */ + dp = &projectors[ numProjectors ]; + memset( dp, 0, sizeof( *dp ) ); + + /* basic setup */ + dp->si = si; + dp->numPlanes = numVerts + 2; + + /* make texture matrix */ + if ( !MakeTextureMatrix( dp, projection, dv[ 0 ], dv[ 1 ], dv[ 2 ] ) ) { + return -1; + } + + /* bound the projector */ + ClearBounds( dp->mins, dp->maxs ); + for ( i = 0; i < numVerts; i++ ) + { + AddPointToBounds( dv[ i ]->xyz, dp->mins, dp->maxs ); + VectorMA( dv[ i ]->xyz, distance, projection, xyz ); + AddPointToBounds( xyz, dp->mins, dp->maxs ); + } + + /* make bouding sphere */ + VectorAdd( dp->mins, dp->maxs, dp->center ); + VectorScale( dp->center, 0.5f, dp->center ); + VectorSubtract( dp->maxs, dp->center, xyz ); + dp->radius = VectorLength( xyz ); + dp->radius2 = dp->radius * dp->radius; + + /* make the front plane */ + if ( !PlaneFromPoints( dp->planes[ 0 ], dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) ) { + return -1; + } + + /* make the back plane */ + VectorSubtract( vec3_origin, dp->planes[ 0 ], dp->planes[ 1 ] ); + VectorMA( dv[ 0 ]->xyz, distance, projection, xyz ); + dp->planes[ 1 ][ 3 ] = DotProduct( xyz, dp->planes[ 1 ] ); + + /* make the side planes */ + for ( i = 0; i < numVerts; i++ ) + { + j = ( i + 1 ) % numVerts; + VectorMA( dv[ i ]->xyz, distance, projection, xyz ); + if ( !PlaneFromPoints( dp->planes[ i + 2 ], dv[ j ]->xyz, dv[ i ]->xyz, xyz ) ) { + return -1; + } + } + + /* return ok */ + numProjectors++; + return numProjectors - 1; +} + + + +/* + ProcessDecals() + finds all decal entities and creates decal projectors + */ + +#define PLANAR_EPSILON 0.5f + +void ProcessDecals( void ){ + int i, j, x, y, pw[ 5 ], r, iterations; + float distance; + vec4_t projection, plane; + vec3_t origin, target, delta; + entity_t *e, *e2; + parseMesh_t *p; + mesh_t *mesh, *subdivided; + bspDrawVert_t *dv[ 4 ]; + const char *value; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- ProcessDecals ---\n" ); + + /* walk entity list */ + for ( i = 0; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + value = ValueForKey( e, "classname" ); + if ( Q_stricmp( value, "_decal" ) ) { + continue; + } + + /* any patches? */ + if ( e->patches == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Decal entity without any patch meshes, ignoring.\n" ); + e->epairs = NULL; /* fixme: leak! */ + continue; + } + + /* find target */ + value = ValueForKey( e, "target" ); + e2 = FindTargetEntity( value ); + + /* no target? */ + if ( e2 == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Decal entity without a valid target, ignoring.\n" ); + continue; + } + + /* walk entity patches */ + for ( p = e->patches; p != NULL; p = e->patches ) + { + /* setup projector */ + if ( VectorCompare( e->origin, vec3_origin ) ) { + VectorAdd( p->eMins, p->eMaxs, origin ); + VectorScale( origin, 0.5f, origin ); + } + else{ + VectorCopy( e->origin, origin ); + } + + VectorCopy( e2->origin, target ); + VectorSubtract( target, origin, delta ); + + /* setup projection plane */ + distance = VectorNormalize( delta, projection ); + projection[ 3 ] = DotProduct( origin, projection ); + + /* create projectors */ + if ( distance > 0.125f ) { + /* tesselate the patch */ + iterations = IterationsForCurve( p->longestCurve, patchSubdivisions ); + subdivided = SubdivideMesh2( p->mesh, iterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* offset by projector origin */ + for ( j = 0; j < ( mesh->width * mesh->height ); j++ ) + VectorAdd( mesh->verts[ j ].xyz, e->origin, mesh->verts[ j ].xyz ); + + /* iterate through the mesh quads */ + for ( y = 0; y < ( mesh->height - 1 ); y++ ) + { + for ( x = 0; x < ( mesh->width - 1 ); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + ( y * mesh->width ); + pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); + pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); + pw[ 3 ] = x + 1 + ( y * mesh->width ); + pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( x + y ) & 1; + + /* get drawverts */ + dv[ 0 ] = &mesh->verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &mesh->verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &mesh->verts[ pw[ r + 2 ] ]; + dv[ 3 ] = &mesh->verts[ pw[ r + 3 ] ]; + + /* planar? (nuking this optimization as it doesn't work on non-rectangular quads) */ + plane[ 0 ] = 0.0f; /* stupid msvc */ + if ( 0 && PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) && + fabs( DotProduct( dv[ 1 ]->xyz, plane ) - plane[ 3 ] ) <= PLANAR_EPSILON ) { + /* make a quad projector */ + MakeDecalProjector( p->shaderInfo, projection, distance, 4, dv ); + } + else + { + /* make first triangle */ + MakeDecalProjector( p->shaderInfo, projection, distance, 3, dv ); + + /* make second triangle */ + dv[ 1 ] = dv[ 2 ]; + dv[ 2 ] = dv[ 3 ]; + MakeDecalProjector( p->shaderInfo, projection, distance, 3, dv ); + } + } + } + + /* clean up */ + free( mesh ); + } + + /* remove patch from entity (fixme: leak!) */ + e->patches = p->next; + + /* push patch to worldspawn (enable this to debug projectors) */ + #if 0 + p->next = entities[ 0 ].patches; + entities[ 0 ].patches = p; + #endif + } + } + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d decal projectors\n", numProjectors ); +} + + + +/* + ProjectDecalOntoWinding() + projects a decal onto a winding + */ + +static void ProjectDecalOntoWinding( decalProjector_t *dp, mapDrawSurface_t *ds, winding_t *w ){ + int i, j; + float d, d2, alpha; + winding_t *front, *back; + mapDrawSurface_t *ds2; + bspDrawVert_t *dv; + vec4_t plane; + + + /* dummy check */ + if ( w->numpoints < 3 ) { + FreeWinding( w ); + return; + } + + /* offset by entity origin */ + for ( i = 0; i < w->numpoints; i++ ) + VectorAdd( w->p[ i ], entityOrigin, w->p[ i ] ); + + /* make a plane from the winding */ + if ( !PlaneFromPoints( plane, w->p[ 0 ], w->p[ 1 ], w->p[ 2 ] ) ) { + FreeWinding( w ); + return; + } + + /* backface check */ + d = DotProduct( dp->planes[ 0 ], plane ); + if ( d < -0.0001f ) { + FreeWinding( w ); + return; + } + + /* walk list of planes */ + for ( i = 0; i < dp->numPlanes; i++ ) + { + /* chop winding by the plane */ + ClipWindingEpsilonStrict( w, dp->planes[ i ], dp->planes[ i ][ 3 ], 0.0625f, &front, &back ); /* strict, if identical plane we don't want to keep it */ + FreeWinding( w ); + + /* lose the front fragment */ + if ( front != NULL ) { + FreeWinding( front ); + } + + /* if nothing left in back, then bail */ + if ( back == NULL ) { + return; + } + + /* reset winding */ + w = back; + } + + /* nothing left? */ + if ( w == NULL || w->numpoints < 3 ) { + return; + } + + /* add to counts */ + numDecalSurfaces++; + + /* make a new surface */ + ds2 = AllocDrawSurface( SURFACE_DECAL ); + + /* set it up */ + ds2->entityNum = ds->entityNum; + ds2->castShadows = ds->castShadows; + ds2->recvShadows = ds->recvShadows; + ds2->shaderInfo = dp->si; + ds2->fogNum = ds->fogNum; /* why was this -1? */ + ds2->cubemapNum = ds->cubemapNum; /* why was this -1? */ + ds2->lightmapScale = ds->lightmapScale; + ds2->shadeAngleDegrees = ds->shadeAngleDegrees; + ds2->numVerts = w->numpoints; + ds2->verts = safe_malloc( ds2->numVerts * sizeof( *ds2->verts ) ); + memset( ds2->verts, 0, ds2->numVerts * sizeof( *ds2->verts ) ); + + /* set vertexes */ + for ( i = 0; i < ds2->numVerts; i++ ) + { + /* get vertex */ + dv = &ds2->verts[ i ]; + + /* set alpha */ + d = DotProduct( w->p[ i ], dp->planes[ 0 ] ) - dp->planes[ 0 ][ 3 ]; + d2 = DotProduct( w->p[ i ], dp->planes[ 1 ] ) - dp->planes[ 1 ][ 3 ]; + alpha = 255.0f * d2 / ( d + d2 ); + if ( alpha > 255 ) { + alpha = 255; + } + else if ( alpha < 0 ) { + alpha = 0; + } + + /* set misc */ + VectorSubtract( w->p[ i ], entityOrigin, dv->xyz ); + VectorCopy( plane, dv->normal ); + dv->st[ 0 ] = DotProduct( dv->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + dv->st[ 1 ] = DotProduct( dv->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + + /* set color */ + for ( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + dv->color[ j ][ 0 ] = 255; + dv->color[ j ][ 1 ] = 255; + dv->color[ j ][ 2 ] = 255; + dv->color[ j ][ 3 ] = alpha; + } + } +} + + + +/* + ProjectDecalOntoFace() + projects a decal onto a brushface surface + */ + +static void ProjectDecalOntoFace( decalProjector_t *dp, mapDrawSurface_t *ds ){ + vec4_t plane; + float d; + winding_t *w; + + + /* dummy check */ + if ( ds->sideRef == NULL || ds->sideRef->side == NULL ) { + return; + } + + /* backface check */ + if ( ds->planar ) { + VectorCopy( mapplanes[ ds->planeNum ].normal, plane ); + plane[ 3 ] = mapplanes[ ds->planeNum ].dist + DotProduct( plane, entityOrigin ); + d = DotProduct( dp->planes[ 0 ], plane ); + if ( d < -0.0001f ) { + return; + } + } + + /* generate decal */ + w = WindingFromDrawSurf( ds ); + ProjectDecalOntoWinding( dp, ds, w ); +} + + + +/* + ProjectDecalOntoPatch() + projects a decal onto a patch surface + */ + +static void ProjectDecalOntoPatch( decalProjector_t *dp, mapDrawSurface_t *ds ){ + int x, y, pw[ 5 ], r, iterations; + vec4_t plane; + float d; + mesh_t src, *mesh, *subdivided; + winding_t *w; + + + /* backface check */ + if ( ds->planar ) { + VectorCopy( mapplanes[ ds->planeNum ].normal, plane ); + plane[ 3 ] = mapplanes[ ds->planeNum ].dist + DotProduct( plane, entityOrigin ); + d = DotProduct( dp->planes[ 0 ], plane ); + if ( d < -0.0001f ) { + return; + } + } + + /* tesselate the patch */ + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.verts = ds->verts; + iterations = IterationsForCurve( ds->longestCurve, patchSubdivisions ); + subdivided = SubdivideMesh2( src, iterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* iterate through the mesh quads */ + for ( y = 0; y < ( mesh->height - 1 ); y++ ) + { + for ( x = 0; x < ( mesh->width - 1 ); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + ( y * mesh->width ); + pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); + pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); + pw[ 3 ] = x + 1 + ( y * mesh->width ); + pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( x + y ) & 1; + + /* generate decal for first triangle */ + w = AllocWinding( 3 ); + w->numpoints = 3; + VectorCopy( mesh->verts[ pw[ r + 0 ] ].xyz, w->p[ 0 ] ); + VectorCopy( mesh->verts[ pw[ r + 1 ] ].xyz, w->p[ 1 ] ); + VectorCopy( mesh->verts[ pw[ r + 2 ] ].xyz, w->p[ 2 ] ); + ProjectDecalOntoWinding( dp, ds, w ); + + /* generate decal for second triangle */ + w = AllocWinding( 3 ); + w->numpoints = 3; + VectorCopy( mesh->verts[ pw[ r + 0 ] ].xyz, w->p[ 0 ] ); + VectorCopy( mesh->verts[ pw[ r + 2 ] ].xyz, w->p[ 1 ] ); + VectorCopy( mesh->verts[ pw[ r + 3 ] ].xyz, w->p[ 2 ] ); + ProjectDecalOntoWinding( dp, ds, w ); + } + } + + /* clean up */ + free( mesh ); +} + + + +/* + ProjectDecalOntoTriangles() + projects a decal onto a triangle surface + */ + +static void ProjectDecalOntoTriangles( decalProjector_t *dp, mapDrawSurface_t *ds ){ + int i; + vec4_t plane; + float d; + winding_t *w; + + + /* triangle surfaces without shaders don't get marks by default */ + if ( ds->type == SURFACE_TRIANGLES && ds->shaderInfo->shaderText == NULL ) { + return; + } + + /* backface check */ + if ( ds->planar ) { + VectorCopy( mapplanes[ ds->planeNum ].normal, plane ); + plane[ 3 ] = mapplanes[ ds->planeNum ].dist + DotProduct( plane, entityOrigin ); + d = DotProduct( dp->planes[ 0 ], plane ); + if ( d < -0.0001f ) { + return; + } + } + + /* iterate through triangles */ + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + /* generate decal */ + w = AllocWinding( 3 ); + w->numpoints = 3; + VectorCopy( ds->verts[ ds->indexes[ i ] ].xyz, w->p[ 0 ] ); + VectorCopy( ds->verts[ ds->indexes[ i + 1 ] ].xyz, w->p[ 1 ] ); + VectorCopy( ds->verts[ ds->indexes[ i + 2 ] ].xyz, w->p[ 2 ] ); + ProjectDecalOntoWinding( dp, ds, w ); + } +} + + + +/* + MakeEntityDecals() + projects decals onto world surfaces + */ + +void MakeEntityDecals( entity_t *e ){ + int i, j, k, f, fOld, start; + decalProjector_t dp; + mapDrawSurface_t *ds; + vec3_t identityAxis[ 3 ] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- MakeEntityDecals ---\n" ); + + /* set entity origin */ + VectorCopy( e->origin, entityOrigin ); + + /* transform projector instead of geometry */ + VectorClear( entityOrigin ); + + /* init pacifier */ + fOld = -1; + start = I_FloatTime(); + + /* walk the list of decal projectors */ + for ( i = 0; i < numProjectors; i++ ) + { + /* print pacifier */ + f = 10 * i / numProjectors; + if ( f != fOld ) { + fOld = f; + Sys_FPrintf( SYS_VRB, "%d...", f ); + } + + /* get projector */ + TransformDecalProjector( &projectors[ i ], identityAxis, e->origin, &dp ); + + /* walk the list of surfaces in the entity */ + for ( j = e->firstDrawSurf; j < numMapDrawSurfs; j++ ) + { + /* get surface */ + ds = &mapDrawSurfs[ j ]; + if ( ds->numVerts <= 0 ) { + continue; + } + + /* ignore autosprite or nomarks */ + if ( ds->shaderInfo->autosprite || ( ds->shaderInfo->compileFlags & C_NOMARKS ) ) { + continue; + } + + /* bounds check */ + for ( k = 0; k < 3; k++ ) + if ( ds->mins[ k ] >= ( dp.center[ k ] + dp.radius ) || + ds->maxs[ k ] <= ( dp.center[ k ] - dp.radius ) ) { + break; + } + if ( k < 3 ) { + continue; + } + + /* switch on type */ + switch ( ds->type ) + { + case SURFACE_FACE: + ProjectDecalOntoFace( &dp, ds ); + break; + + case SURFACE_PATCH: + ProjectDecalOntoPatch( &dp, ds ); + break; + + case SURFACE_TRIANGLES: + case SURFACE_FORCED_META: + case SURFACE_META: + ProjectDecalOntoTriangles( &dp, ds ); + break; + + default: + break; + } + } + } + + /* print time */ + Sys_FPrintf( SYS_VRB, " (%d)\n", (int) ( I_FloatTime() - start ) ); + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d decal surfaces\n", numDecalSurfaces ); +} diff --git a/tools/vmap/exportents.c b/tools/vmap/exportents.c new file mode 100644 index 0000000..8452e48 --- /dev/null +++ b/tools/vmap/exportents.c @@ -0,0 +1,112 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2013 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define EXPORTENTS_C + + + +/* dependencies */ +#include "vmap.h" + + + + +/* ------------------------------------------------------------------------------- + + this file contains code that exports entities to a .ent file. + + ------------------------------------------------------------------------------- */ + +/* + ExportEntities() + exports the entities to a text file (.ent) + */ + +void ExportEntities( void ){ + char filename[ 1024 ]; + FILE *file; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- ExportEntities ---\n" ); + + /* do some path mangling */ + strcpy( filename, source ); + StripExtension( filename ); + strcat( filename, ".ent" ); + + /* sanity check */ + if ( bspEntData == NULL || bspEntDataSize == 0 ) { + Sys_FPrintf( SYS_WRN, "WARNING: No BSP entity data. aborting...\n" ); + return; + } + + /* write it */ + Sys_Printf( "Writing %s\n", filename ); + Sys_FPrintf( SYS_VRB, "(%d bytes)\n", bspEntDataSize ); + file = fopen( filename, "w" ); + + if ( file == NULL ) { + Error( "Unable to open %s for writing", filename ); + } + + fprintf( file, "%s\n", bspEntData ); + fclose( file ); +} + + + +/* + ExportEntitiesMain() + exports the entities to a text file (.ent) + */ + +int ExportEntitiesMain( int argc, char **argv ){ + /* arg checking */ + if ( argc < 1 ) { + Sys_Printf( "Usage: q3map -exportents [-v] \n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + + /* export the lightmaps */ + ExportEntities(); + + /* return to sender */ + return 0; +} \ No newline at end of file diff --git a/tools/vmap/facebsp.c b/tools/vmap/facebsp.c new file mode 100644 index 0000000..040ebc6 --- /dev/null +++ b/tools/vmap/facebsp.c @@ -0,0 +1,513 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define FACEBSP_C + + + +/* dependencies */ +#include "vmap.h" + + + +int c_faceLeafs; + + +/* + ================ + AllocBspFace + ================ + */ +face_t *AllocBspFace( void ) { + face_t *f; + + f = safe_malloc( sizeof( *f ) ); + memset( f, 0, sizeof( *f ) ); + + return f; +} + + + +/* + ================ + FreeBspFace + ================ + */ +void FreeBspFace( face_t *f ) { + if ( f->w ) { + FreeWinding( f->w ); + } + free( f ); +} + + + +/* + SelectSplitPlaneNum() + finds the best split plane for this node + */ + +static void SelectSplitPlaneNum( node_t *node, face_t *list, int *splitPlaneNum, int *compileFlags ){ + face_t *split; + face_t *check; + face_t *bestSplit; + int splits, facing, front, back; + int side; + plane_t *plane; + int value, bestValue; + int i; + vec3_t normal; + float dist; + int planenum; + float sizeBias; + + /* ydnar: set some defaults */ + *splitPlaneNum = -1; /* leaf */ + *compileFlags = 0; + + /* ydnar 2002-06-24: changed this to split on z-axis as well */ + /* ydnar 2002-09-21: changed blocksize to be a vector, so mappers can specify a 3 element value */ + + /* if it is crossing a block boundary, force a split */ + for ( i = 0; i < 3; i++ ) + { + if ( blockSize[ i ] <= 0 ) { + continue; + } + dist = blockSize[ i ] * ( floor( node->mins[ i ] / blockSize[ i ] ) + 1 ); + if ( node->maxs[ i ] > dist ) { + VectorClear( normal ); + normal[ i ] = 1; + planenum = FindFloatPlane( normal, dist, 0, NULL ); + *splitPlaneNum = planenum; + return; + } + } + + /* pick one of the face planes */ + bestValue = -99999; + bestSplit = list; + + + // div0: this check causes detail/structural mixes + //for( split = list; split; split = split->next ) + // split->checked = qfalse; + + for ( split = list; split; split = split->next ) + { + //if ( split->checked ) + // continue; + + plane = &mapplanes[ split->planenum ]; + splits = 0; + facing = 0; + front = 0; + back = 0; + for ( check = list ; check ; check = check->next ) { + if ( check->planenum == split->planenum ) { + facing++; + //check->checked = qtrue; // won't need to test this plane again + continue; + } + side = WindingOnPlaneSide( check->w, plane->normal, plane->dist ); + if ( side == SIDE_CROSS ) { + splits++; + } + else if ( side == SIDE_FRONT ) { + front++; + } + else if ( side == SIDE_BACK ) { + back++; + } + } + + if ( bspAlternateSplitWeights ) { + // from 27 + + //Bigger is better + sizeBias = WindingArea( split->w ); + + //Base score = 20000 perfectly balanced + value = 20000 - ( abs( front - back ) ); + value -= plane->counter; // If we've already used this plane sometime in the past try not to use it again + value -= facing ; // if we're going to have alot of other surfs use this plane, we want to get it in quickly. + value -= splits * 5; //more splits = bad + value += sizeBias * 10; //We want a huge score bias based on plane size + } + else + { + value = 5 * facing - 5 * splits; // - abs(front-back); + if ( plane->type < 3 ) { + value += 5; // axial is better + } + } + + value += split->priority; // prioritize hints higher + + if ( value > bestValue ) { + bestValue = value; + bestSplit = split; + } + } + + /* nothing, we have a leaf */ + if ( bestValue == -99999 ) { + return; + } + + /* set best split data */ + *splitPlaneNum = bestSplit->planenum; + *compileFlags = bestSplit->compileFlags; + + if ( *splitPlaneNum > -1 ) { + mapplanes[ *splitPlaneNum ].counter++; + } +} + + + +/* + CountFaceList() + counts bsp faces in the linked list + */ + +int CountFaceList( face_t *list ){ + int c; + + + c = 0; + for ( ; list != NULL; list = list->next ) + c++; + return c; +} + + + +/* + BuildFaceTree_r() + recursively builds the bsp, splitting on face planes + */ + +void BuildFaceTree_r( node_t *node, face_t *list ){ + face_t *split; + face_t *next; + int side; + plane_t *plane; + face_t *newFace; + face_t *childLists[2]; + winding_t *frontWinding, *backWinding; + int i; + int splitPlaneNum, compileFlags; + + + /* count faces left */ + i = CountFaceList( list ); + + /* select the best split plane */ + SelectSplitPlaneNum( node, list, &splitPlaneNum, &compileFlags ); + + /* if we don't have any more faces, this is a node */ + if ( splitPlaneNum == -1 ) { + node->planenum = PLANENUM_LEAF; + node->has_structural_children = qfalse; + c_faceLeafs++; + return; + } + + /* partition the list */ + node->planenum = splitPlaneNum; + node->compileFlags = compileFlags; + node->has_structural_children = !( compileFlags & C_DETAIL ) && !node->opaque; + plane = &mapplanes[ splitPlaneNum ]; + childLists[0] = NULL; + childLists[1] = NULL; + + for ( split = list; split; split = next ) + { + /* set next */ + next = split->next; + + /* don't split by identical plane */ + if ( split->planenum == node->planenum ) { + FreeBspFace( split ); + continue; + } + + /* determine which side the face falls on */ + side = WindingOnPlaneSide( split->w, plane->normal, plane->dist ); + + /* switch on side */ + if ( side == SIDE_CROSS ) { + /* strict; if no winding is left, we have a "virtually identical" plane and don't want to split by it */ + ClipWindingEpsilonStrict( split->w, plane->normal, plane->dist, CLIP_EPSILON * 2, + &frontWinding, &backWinding ); + if ( frontWinding ) { + newFace = AllocBspFace(); + newFace->w = frontWinding; + newFace->next = childLists[0]; + newFace->planenum = split->planenum; + newFace->priority = split->priority; + newFace->compileFlags = split->compileFlags; + childLists[0] = newFace; + } + if ( backWinding ) { + newFace = AllocBspFace(); + newFace->w = backWinding; + newFace->next = childLists[1]; + newFace->planenum = split->planenum; + newFace->priority = split->priority; + newFace->compileFlags = split->compileFlags; + childLists[1] = newFace; + } + FreeBspFace( split ); + } + else if ( side == SIDE_FRONT ) { + split->next = childLists[0]; + childLists[0] = split; + } + else if ( side == SIDE_BACK ) { + split->next = childLists[1]; + childLists[1] = split; + } + } + + + // recursively process children + for ( i = 0 ; i < 2 ; i++ ) { + node->children[i] = AllocNode(); + node->children[i]->parent = node; + VectorCopy( node->mins, node->children[i]->mins ); + VectorCopy( node->maxs, node->children[i]->maxs ); + } + + for ( i = 0 ; i < 3 ; i++ ) { + if ( plane->normal[i] == 1 ) { + node->children[0]->mins[i] = plane->dist; + node->children[1]->maxs[i] = plane->dist; + break; + } + if ( plane->normal[i] == -1 ) { + node->children[0]->maxs[i] = -plane->dist; + node->children[1]->mins[i] = -plane->dist; + break; + } + } + + for ( i = 0 ; i < 2 ; i++ ) { + BuildFaceTree_r( node->children[i], childLists[i] ); + node->has_structural_children |= node->children[i]->has_structural_children; + } +} + + +/* + ================ + FaceBSP + + List will be freed before returning + ================ + */ +tree_t *FaceBSP( face_t *list ) { + tree_t *tree; + face_t *face; + int i; + int count; + + Sys_FPrintf( SYS_VRB, "--- FaceBSP ---\n" ); + + tree = AllocTree(); + + count = 0; + for ( face = list; face != NULL; face = face->next ) + { + count++; + for ( i = 0; i < face->w->numpoints; i++ ) + { + AddPointToBounds( face->w->p[ i ], tree->mins, tree->maxs ); + } + } + Sys_FPrintf( SYS_VRB, "%9d faces\n", count ); + + for ( i = 0; i < nummapplanes; i++ ) + { + mapplanes[ i ].counter = 0; + } + + tree->headnode = AllocNode(); + VectorCopy( tree->mins, tree->headnode->mins ); + VectorCopy( tree->maxs, tree->headnode->maxs ); + c_faceLeafs = 0; + + BuildFaceTree_r( tree->headnode, list ); + + Sys_FPrintf( SYS_VRB, "%9d leafs\n", c_faceLeafs ); + + return tree; +} + + + +/* + MakeStructuralBSPFaceList() + get structural brush faces + */ + +face_t *MakeStructuralBSPFaceList( brush_t *list ){ + brush_t *b; + int i; + side_t *s; + winding_t *w; + face_t *f, *flist; + + + flist = NULL; + for ( b = list; b != NULL; b = b->next ) + { + if ( !deepBSP && b->detail ) { + continue; + } + + for ( i = 0; i < b->numsides; i++ ) + { + /* get side and winding */ + s = &b->sides[ i ]; + w = s->winding; + if ( w == NULL ) { + continue; + } + + /* ydnar: skip certain faces */ + if ( s->compileFlags & C_SKIP ) { + continue; + } + + /* allocate a face */ + f = AllocBspFace(); + f->w = CopyWinding( w ); + f->planenum = s->planenum & ~1; + f->compileFlags = s->compileFlags; /* ydnar */ + if ( b->detail ) { + f->compileFlags |= C_DETAIL; + } + + /* ydnar: set priority */ + f->priority = 0; + if ( f->compileFlags & C_HINT ) { + f->priority += HINT_PRIORITY; + } + if ( f->compileFlags & C_ANTIPORTAL ) { + f->priority += ANTIPORTAL_PRIORITY; + } + if ( f->compileFlags & C_AREAPORTAL ) { + f->priority += AREAPORTAL_PRIORITY; + } + if ( f->compileFlags & C_DETAIL ) { + f->priority += DETAIL_PRIORITY; + } + + /* get next face */ + f->next = flist; + flist = f; + } + } + + return flist; +} + + + +/* + MakeVisibleBSPFaceList() + get visible brush faces + */ + +face_t *MakeVisibleBSPFaceList( brush_t *list ){ + brush_t *b; + int i; + side_t *s; + winding_t *w; + face_t *f, *flist; + + + flist = NULL; + for ( b = list; b != NULL; b = b->next ) + { + if ( !deepBSP && b->detail ) { + continue; + } + + for ( i = 0; i < b->numsides; i++ ) + { + /* get side and winding */ + s = &b->sides[ i ]; + w = s->visibleHull; + if ( w == NULL ) { + continue; + } + + /* ydnar: skip certain faces */ + if ( s->compileFlags & C_SKIP ) { + continue; + } + + /* allocate a face */ + f = AllocBspFace(); + f->w = CopyWinding( w ); + f->planenum = s->planenum & ~1; + f->compileFlags = s->compileFlags; /* ydnar */ + if ( b->detail ) { + f->compileFlags |= C_DETAIL; + } + + /* ydnar: set priority */ + f->priority = 0; + if ( f->compileFlags & C_HINT ) { + f->priority += HINT_PRIORITY; + } + if ( f->compileFlags & C_ANTIPORTAL ) { + f->priority += ANTIPORTAL_PRIORITY; + } + if ( f->compileFlags & C_AREAPORTAL ) { + f->priority += AREAPORTAL_PRIORITY; + } + if ( f->compileFlags & C_DETAIL ) { + f->priority += DETAIL_PRIORITY; + } + + /* get next face */ + f->next = flist; + flist = f; + } + } + + return flist; +} diff --git a/tools/vmap/fixaas.c b/tools/vmap/fixaas.c new file mode 100644 index 0000000..ec2ffdd --- /dev/null +++ b/tools/vmap/fixaas.c @@ -0,0 +1,110 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* dependencies */ +#include "vmap.h" + + + +/* + MD4BlockChecksum() + calculates an md4 checksum for a block of data + */ + +static int MD4BlockChecksum( void *buffer, int length ){ + return Com_BlockChecksum( buffer, length ); +} + +/* + FixAASMain() + resets an aas checksum to match the given BSP + */ + +int FixAASMain( int argc, char **argv ){ + int length, checksum; + void *buffer; + FILE *file; + char aas[ 1024 ], **ext; + char *exts[] = + { + ".aas", + "_b0.aas", + "_b1.aas", + NULL + }; + + + /* arg checking */ + if ( argc < 2 ) { + Sys_Printf( "Usage: q3map -fixaas [-v] \n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* note it */ + Sys_Printf( "--- FixAAS ---\n" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + length = LoadFile( source, &buffer ); + + /* create bsp checksum */ + Sys_Printf( "Creating checksum...\n" ); + checksum = LittleLong( MD4BlockChecksum( buffer, length ) ); + + /* write checksum to aas */ + ext = exts; + while ( *ext ) + { + /* mangle name */ + strcpy( aas, source ); + StripExtension( aas ); + strcat( aas, *ext ); + Sys_Printf( "Trying %s\n", aas ); + ext++; + + /* fix it */ + file = fopen( aas, "r+b" ); + if ( !file ) { + continue; + } + if ( fwrite( &checksum, 4, 1, file ) != 1 ) { + Error( "Error writing checksum to %s", aas ); + } + fclose( file ); + } + + /* return to sender */ + return 0; +} diff --git a/tools/vmap/fog.c b/tools/vmap/fog.c new file mode 100644 index 0000000..a7e6993 --- /dev/null +++ b/tools/vmap/fog.c @@ -0,0 +1,888 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define FOG_C + + + +/* dependencies */ +#include "vmap.h" + + + +int numFogFragments; +int numFogPatchFragments; + + + +/* + DrawSurfToMesh() + converts a patch drawsurface to a mesh_t + */ + +mesh_t *DrawSurfToMesh( mapDrawSurface_t *ds ){ + mesh_t *m; + + + m = safe_malloc( sizeof( *m ) ); + m->width = ds->patchWidth; + m->height = ds->patchHeight; + m->verts = safe_malloc( sizeof( m->verts[ 0 ] ) * m->width * m->height ); + memcpy( m->verts, ds->verts, sizeof( m->verts[ 0 ] ) * m->width * m->height ); + + return m; +} + + + +/* + SplitMeshByPlane() + chops a mesh by a plane + */ + +void SplitMeshByPlane( mesh_t *in, vec3_t normal, float dist, mesh_t **front, mesh_t **back ){ + int w, h, split; + float d[MAX_PATCH_SIZE][MAX_PATCH_SIZE]; + bspDrawVert_t *dv, *v1, *v2; + int c_front, c_back, c_on; + mesh_t *f, *b; + int i; + float frac; + int frontAprox, backAprox; + + for ( i = 0 ; i < 2 ; i++ ) { + dv = in->verts; + c_front = 0; + c_back = 0; + c_on = 0; + for ( h = 0 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width ; w++, dv++ ) { + d[h][w] = DotProduct( dv->xyz, normal ) - dist; + if ( d[h][w] > ON_EPSILON ) { + c_front++; + } + else if ( d[h][w] < -ON_EPSILON ) { + c_back++; + } + else { + c_on++; + } + } + } + + *front = NULL; + *back = NULL; + + if ( !c_front ) { + *back = in; + return; + } + if ( !c_back ) { + *front = in; + return; + } + + // find a split point + split = -1; + for ( w = 0 ; w < in->width - 1 ; w++ ) { + if ( ( d[0][w] < 0 ) != ( d[0][w + 1] < 0 ) ) { + if ( split == -1 ) { + split = w; + break; + } + } + } + + if ( split == -1 ) { + if ( i == 1 ) { + Sys_FPrintf( SYS_VRB, "No crossing points in patch\n" ); + *front = in; + return; + } + + in = TransposeMesh( in ); + InvertMesh( in ); + continue; + } + + // make sure the split point stays the same for all other rows + for ( h = 1 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width - 1 ; w++ ) { + if ( ( d[h][w] < 0 ) != ( d[h][w + 1] < 0 ) ) { + if ( w != split ) { + Sys_Printf( "multiple crossing points for patch -- can't clip\n" ); + *front = in; + return; + } + } + } + if ( ( d[h][split] < 0 ) == ( d[h][split + 1] < 0 ) ) { + Sys_Printf( "differing crossing points for patch -- can't clip\n" ); + *front = in; + return; + } + } + + break; + } + + + // create two new meshes + f = safe_malloc( sizeof( *f ) ); + f->width = split + 2; + if ( !( f->width & 1 ) ) { + f->width++; + frontAprox = 1; + } + else { + frontAprox = 0; + } + if ( f->width > MAX_PATCH_SIZE ) { + Error( "MAX_PATCH_SIZE after split" ); + } + f->height = in->height; + f->verts = safe_malloc( sizeof( f->verts[0] ) * f->width * f->height ); + + b = safe_malloc( sizeof( *b ) ); + b->width = in->width - split; + if ( !( b->width & 1 ) ) { + b->width++; + backAprox = 1; + } + else { + backAprox = 0; + } + if ( b->width > MAX_PATCH_SIZE ) { + Error( "MAX_PATCH_SIZE after split" ); + } + b->height = in->height; + b->verts = safe_malloc( sizeof( b->verts[0] ) * b->width * b->height ); + + if ( d[0][0] > 0 ) { + *front = f; + *back = b; + } + else { + *front = b; + *back = f; + } + + // distribute the points + for ( w = 0 ; w < in->width ; w++ ) { + for ( h = 0 ; h < in->height ; h++ ) { + if ( w <= split ) { + f->verts[ h * f->width + w ] = in->verts[ h * in->width + w ]; + } + else { + b->verts[ h * b->width + w - split + backAprox ] = in->verts[ h * in->width + w ]; + } + } + } + + // clip the crossing line + for ( h = 0; h < in->height; h++ ) + { + dv = &f->verts[ h * f->width + split + 1 ]; + v1 = &in->verts[ h * in->width + split ]; + v2 = &in->verts[ h * in->width + split + 1 ]; + + frac = d[h][split] / ( d[h][split] - d[h][split + 1] ); + + /* interpolate */ + //% for( i = 0; i < 10; i++ ) + //% dv->xyz[ i ] = v1->xyz[ i ] + frac * (v2->xyz[ i ] - v1->xyz[ i ]); + //% dv->xyz[10] = 0; // set all 4 colors to 0 + LerpDrawVertAmount( v1, v2, frac, dv ); + + if ( frontAprox ) { + f->verts[ h * f->width + split + 2 ] = *dv; + } + b->verts[ h * b->width ] = *dv; + if ( backAprox ) { + b->verts[ h * b->width + 1 ] = *dv; + } + } + + /* + PrintMesh( in ); + Sys_Printf("\n"); + PrintMesh( f ); + Sys_Printf("\n"); + PrintMesh( b ); + Sys_Printf("\n"); + */ + + FreeMesh( in ); +} + + +/* + ChopPatchSurfaceByBrush() + chops a patch up by a fog brush + */ + +qboolean ChopPatchSurfaceByBrush( entity_t *e, mapDrawSurface_t *ds, brush_t *b ){ + int i, j; + side_t *s; + plane_t *plane; + mesh_t *outside[MAX_BRUSH_SIDES]; + int numOutside; + mesh_t *m, *front, *back; + mapDrawSurface_t *newds; + + m = DrawSurfToMesh( ds ); + numOutside = 0; + + // only split by the top and bottom planes to avoid + // some messy patch clipping issues + + for ( i = 4 ; i <= 5 ; i++ ) { + s = &b->sides[ i ]; + plane = &mapplanes[ s->planenum ]; + + SplitMeshByPlane( m, plane->normal, plane->dist, &front, &back ); + + if ( !back ) { + // nothing actually contained inside + for ( j = 0 ; j < numOutside ; j++ ) { + FreeMesh( outside[j] ); + } + return qfalse; + } + m = back; + + if ( front ) { + if ( numOutside == MAX_BRUSH_SIDES ) { + Error( "MAX_BRUSH_SIDES" ); + } + outside[ numOutside ] = front; + numOutside++; + } + } + + /* all of outside fragments become seperate drawsurfs */ + numFogPatchFragments += numOutside; + for ( i = 0; i < numOutside; i++ ) + { + /* transpose and invert the chopped patch (fixes potential crash. fixme: why?) */ + outside[ i ] = TransposeMesh( outside[ i ] ); + InvertMesh( outside[ i ] ); + + /* ydnar: do this the hacky right way */ + newds = AllocDrawSurface( SURFACE_PATCH ); + memcpy( newds, ds, sizeof( *ds ) ); + newds->patchWidth = outside[ i ]->width; + newds->patchHeight = outside[ i ]->height; + newds->numVerts = outside[ i ]->width * outside[ i ]->height; + newds->verts = safe_malloc( newds->numVerts * sizeof( *newds->verts ) ); + memcpy( newds->verts, outside[ i ]->verts, newds->numVerts * sizeof( *newds->verts ) ); + + /* free the source mesh */ + FreeMesh( outside[ i ] ); + } + + /* only rejigger this patch if it was chopped */ + //% Sys_Printf( "Inside: %d x %d\n", m->width, m->height ); + if ( numOutside > 0 ) { + /* transpose and invert the chopped patch (fixes potential crash. fixme: why?) */ + m = TransposeMesh( m ); + InvertMesh( m ); + + /* replace ds with m */ + ds->patchWidth = m->width; + ds->patchHeight = m->height; + ds->numVerts = m->width * m->height; + free( ds->verts ); + ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) ); + memcpy( ds->verts, m->verts, ds->numVerts * sizeof( *ds->verts ) ); + } + + /* free the source mesh and return */ + FreeMesh( m ); + return qtrue; +} + + + +/* + WindingFromDrawSurf() + creates a winding from a surface's verts + */ + +winding_t *WindingFromDrawSurf( mapDrawSurface_t *ds ){ + winding_t *w; + int i; + + // we use the first point of the surface, maybe something more clever would be useful + // (actually send the whole draw surface would be cool?) + if ( ds->numVerts >= MAX_POINTS_ON_WINDING ) { + int max = ds->numVerts; + vec3_t p[256]; + + if ( max > 256 ) { + max = 256; + } + + for ( i = 0 ; i < max ; i++ ) { + VectorCopy( ds->verts[i].xyz, p[i] ); + } + + xml_Winding( "WindingFromDrawSurf failed: MAX_POINTS_ON_WINDING exceeded", p, max, qtrue ); + } + + w = AllocWinding( ds->numVerts ); + w->numpoints = ds->numVerts; + for ( i = 0 ; i < ds->numVerts ; i++ ) { + VectorCopy( ds->verts[i].xyz, w->p[i] ); + } + return w; +} + + + +/* + ChopFaceSurfaceByBrush() + chops up a face drawsurface by a fog brush, with a potential fragment left inside + */ + +qboolean ChopFaceSurfaceByBrush( entity_t *e, mapDrawSurface_t *ds, brush_t *b ){ + int i, j; + side_t *s; + plane_t *plane; + winding_t *w; + winding_t *front, *back; + winding_t *outside[ MAX_BRUSH_SIDES ]; + int numOutside; + mapDrawSurface_t *newds; + + + /* dummy check */ + if ( ds->sideRef == NULL || ds->sideRef->side == NULL ) { + return qfalse; + } + + /* initial setup */ + w = WindingFromDrawSurf( ds ); + numOutside = 0; + + /* chop by each brush side */ + for ( i = 0; i < b->numsides; i++ ) + { + /* get brush side and plane */ + s = &b->sides[ i ]; + plane = &mapplanes[ s->planenum ]; + + /* handle coplanar outfacing (don't fog) */ + if ( ds->sideRef->side->planenum == s->planenum ) { + return qfalse; + } + + /* handle coplanar infacing (keep inside) */ + if ( ( ds->sideRef->side->planenum ^ 1 ) == s->planenum ) { + continue; + } + + /* general case */ + ClipWindingEpsilonStrict( w, plane->normal, plane->dist, ON_EPSILON, &front, &back ); /* strict; if plane is "almost identical" to face, both ways to continue can be wrong, so we better not fog it */ + FreeWinding( w ); + + if ( back == NULL ) { + /* nothing actually contained inside */ + for ( j = 0; j < numOutside; j++ ) + FreeWinding( outside[ j ] ); + return qfalse; + } + + if ( front != NULL ) { + if ( numOutside == MAX_BRUSH_SIDES ) { + Error( "MAX_BRUSH_SIDES" ); + } + outside[ numOutside ] = front; + numOutside++; + } + + w = back; + } + + /* fixme: celshaded surface fragment errata */ + + /* all of outside fragments become seperate drawsurfs */ + numFogFragments += numOutside; + s = ds->sideRef->side; + for ( i = 0; i < numOutside; i++ ) + { + newds = DrawSurfaceForSide( e, ds->mapBrush, s, outside[ i ] ); + newds->fogNum = ds->fogNum; + FreeWinding( outside[ i ] ); + } + + /* ydnar: the old code neglected to snap to 0.125 for the fragment + inside the fog brush, leading to sparklies. this new code does + the right thing and uses the original surface's brush side */ + + /* build a drawsurf for it */ + newds = DrawSurfaceForSide( e, ds->mapBrush, s, w ); + if ( newds == NULL ) { + return qfalse; + } + + /* copy new to original */ + ClearSurface( ds ); + memcpy( ds, newds, sizeof( mapDrawSurface_t ) ); + + /* didn't really add a new drawsurface... :) */ + numMapDrawSurfs--; + + /* return ok */ + return qtrue; +} + + + +/* + FogDrawSurfaces() + call after the surface list has been pruned, before tjunction fixing + */ + +void FogDrawSurfaces( entity_t *e ){ + int i, j, k, fogNum; + fog_t *fog; + mapDrawSurface_t *ds; + vec3_t mins, maxs; + int fogged, numFogged; + int numBaseDrawSurfs; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "----- FogDrawSurfs -----\n" ); + + /* reset counters */ + numFogged = 0; + numFogFragments = 0; + + /* walk fog list */ + for ( fogNum = 0; fogNum < numMapFogs; fogNum++ ) + { + /* get fog */ + fog = &mapFogs[ fogNum ]; + + /* clip each surface into this, but don't clip any of the resulting fragments to the same brush */ + numBaseDrawSurfs = numMapDrawSurfs; + for ( i = 0; i < numBaseDrawSurfs; i++ ) + { + /* get the drawsurface */ + ds = &mapDrawSurfs[ i ]; + + /* no fog? */ + if ( ds->shaderInfo->noFog ) { + continue; + } + + /* global fog doesn't have a brush */ + if ( fog->brush == NULL ) { + /* don't re-fog already fogged surfaces */ + if ( ds->fogNum >= 0 ) { + continue; + } + fogged = 1; + } + else + { + /* find drawsurface bounds */ + ClearBounds( mins, maxs ); + for ( j = 0; j < ds->numVerts; j++ ) + AddPointToBounds( ds->verts[ j ].xyz, mins, maxs ); + + /* check against the fog brush */ + for ( k = 0; k < 3; k++ ) + { + if ( mins[ k ] > fog->brush->maxs[ k ] ) { + break; + } + if ( maxs[ k ] < fog->brush->mins[ k ] ) { + break; + } + } + + /* no intersection? */ + if ( k < 3 ) { + continue; + } + + /* ydnar: gs mods: handle the various types of surfaces */ + switch ( ds->type ) + { + /* handle brush faces */ + case SURFACE_FACE: + fogged = ChopFaceSurfaceByBrush( e, ds, fog->brush ); + break; + + /* handle patches */ + case SURFACE_PATCH: + fogged = ChopPatchSurfaceByBrush( e, ds, fog->brush ); + break; + + /* handle triangle surfaces (fixme: split triangle surfaces) */ + case SURFACE_TRIANGLES: + case SURFACE_FORCED_META: + case SURFACE_META: + fogged = 1; + break; + + /* no fogging */ + default: + fogged = 0; + break; + } + } + + /* is this surface fogged? */ + if ( fogged ) { + numFogged += fogged; + ds->fogNum = fogNum; + } + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d fog polygon fragments\n", numFogFragments ); + Sys_FPrintf( SYS_VRB, "%9d fog patch fragments\n", numFogPatchFragments ); + Sys_FPrintf( SYS_VRB, "%9d fogged drawsurfs\n", numFogged ); +} + + +#if 0 +/* + FogForPoint() - ydnar + gets the fog number for a point in space + */ + +int FogForPoint( vec3_t point, float epsilon ){ + int fogNum, i, j; + float dot; + qboolean inside; + brush_t *brush; + plane_t *plane; + + + /* start with bogus fog num */ + fogNum = defaultFogNum; + + /* walk the list of fog volumes */ + for ( i = 0; i < numMapFogs; i++ ) + { + /* sof2: global fog doesn't reference a brush */ + if ( mapFogs[ i ].brush == NULL ) { + fogNum = i; + continue; + } + + /* get fog brush */ + brush = mapFogs[ i ].brush; + + /* check point against all planes */ + inside = qtrue; + for ( j = 0; j < brush->numsides && inside; j++ ) + { + plane = &mapplanes[ brush->sides[ j ].planenum ]; /* note usage of map planes here */ + dot = DotProduct( point, plane->normal ); + dot -= plane->dist; + if ( dot > epsilon ) { + inside = qfalse; + } + } + + /* if inside, return the fog num */ + if ( inside ) { + //% Sys_Printf( "FogForPoint: %f, %f, %f in fog %d\n", point[ 0 ], point[ 1 ], point[ 2 ], i ); + return i; + } + } + + /* if the point made it this far, it's not inside any fog volumes (or inside global fog) */ + return fogNum; +} +#endif + + +/* + FogForBounds() - ydnar + gets the fog number for a bounding box + */ + +int FogForBounds( vec3_t mins, vec3_t maxs, float epsilon ){ + int fogNum, i, j; + float highMin, lowMax, volume, bestVolume; + vec3_t fogMins, fogMaxs, overlap; + brush_t *brush; + + + /* start with bogus fog num */ + fogNum = defaultFogNum; + + /* init */ + bestVolume = 0.0f; + + /* walk the list of fog volumes */ + for ( i = 0; i < numMapFogs; i++ ) + { + /* sof2: global fog doesn't reference a brush */ + if ( mapFogs[ i ].brush == NULL ) { + fogNum = i; + continue; + } + + /* get fog brush */ + brush = mapFogs[ i ].brush; + + /* get bounds */ + fogMins[ 0 ] = brush->mins[ 0 ] - epsilon; + fogMins[ 1 ] = brush->mins[ 1 ] - epsilon; + fogMins[ 2 ] = brush->mins[ 2 ] - epsilon; + fogMaxs[ 0 ] = brush->maxs[ 0 ] + epsilon; + fogMaxs[ 1 ] = brush->maxs[ 1 ] + epsilon; + fogMaxs[ 2 ] = brush->maxs[ 2 ] + epsilon; + + /* check against bounds */ + for ( j = 0; j < 3; j++ ) + { + if ( mins[ j ] > fogMaxs[ j ] || maxs[ j ] < fogMins[ j ] ) { + break; + } + highMin = mins[ j ] > fogMins[ j ] ? mins[ j ] : fogMins[ j ]; + lowMax = maxs[ j ] < fogMaxs[ j ] ? maxs[ j ] : fogMaxs[ j ]; + overlap[ j ] = lowMax - highMin; + if ( overlap[ j ] < 1.0f ) { + overlap[ j ] = 1.0f; + } + } + + /* no overlap */ + if ( j < 3 ) { + continue; + } + + /* get volume */ + volume = overlap[ 0 ] * overlap[ 1 ] * overlap[ 2 ]; + + /* test against best volume */ + if ( volume > bestVolume ) { + bestVolume = volume; + fogNum = i; + } + } + + /* if the point made it this far, it's not inside any fog volumes (or inside global fog) */ + return fogNum; +} + + + +/* + CreateMapFogs() - ydnar + generates a list of map fogs + */ + +void CreateMapFogs( void ){ + int i; + entity_t *entity; + brush_t *brush; + fog_t *fog; + vec3_t invFogDir; + const char *globalFog; + + + /* skip? */ + if ( nofog ) { + return; + } + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- CreateMapFogs ---\n" ); + + /* walk entities */ + for ( i = 0; i < numEntities; i++ ) + { + /* get entity */ + entity = &entities[ i ]; + + /* walk entity brushes */ + for ( brush = entity->brushes; brush != NULL; brush = brush->next ) + { + /* ignore non-fog brushes */ + if ( brush->contentShader->fogParms == qfalse ) { + continue; + } + + /* test limit */ + if ( numMapFogs >= MAX_MAP_FOGS ) { + Error( "Exceeded MAX_MAP_FOGS (%d)", MAX_MAP_FOGS ); + } + + /* set up fog */ + fog = &mapFogs[ numMapFogs++ ]; + fog->si = brush->contentShader; + fog->brush = brush; + fog->visibleSide = -1; + + /* if shader specifies an explicit direction, then find a matching brush side with an opposed normal */ + if ( VectorLength( fog->si->fogDir ) ) { + /* flip it */ + VectorScale( fog->si->fogDir, -1.0f, invFogDir ); + + /* find the brush side */ + for ( i = 0; i < brush->numsides; i++ ) + { + if ( VectorCompare( invFogDir, mapplanes[ brush->sides[ i ].planenum ].normal ) ) { + fog->visibleSide = i; + //% Sys_Printf( "Brush num: %d Side num: %d\n", fog->brushNum, fog->visibleSide ); + break; + } + } + } + } + } + + /* ydnar: global fog */ + globalFog = ValueForKey( &entities[ 0 ], "fog" ); + + if ( globalFog[ 0 ] != '\0' ) { + /* test limit */ + if ( numMapFogs >= MAX_MAP_FOGS ) { + Error( "Exceeded MAX_MAP_FOGS (%d) trying to add global fog", MAX_MAP_FOGS ); + } + + /* note it */ + Sys_FPrintf( SYS_VRB, "Map has global fog shader %s\n", globalFog ); + + /* set up fog */ + fog = &mapFogs[ numMapFogs++ ]; + fog->si = ShaderInfoForShaderNull( globalFog ); + if ( fog->si == NULL ) { + Error( "Invalid shader \"%s\" referenced trying to add global fog", globalFog ); + } + fog->brush = NULL; + fog->visibleSide = -1; + + /* set as default fog */ + defaultFogNum = numMapFogs - 1; + + /* mark all worldspawn brushes as fogged */ + for ( brush = entities[ 0 ].brushes; brush != NULL; brush = brush->next ) + ApplySurfaceParm( "fog", &brush->contentFlags, NULL, &brush->compileFlags ); + } + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d fogs\n", numMapFogs ); +} + + + + + + + +/* + CubemapForBounds() - spike + just finds the closest cubemap. + */ + +int CubemapForBounds( vec3_t mins, vec3_t maxs ){ + int cm = -1; + float cmdist = FLT_MAX, d; + int i; + vec3_t mid, move; + VectorMid(mins, maxs, mid); + for (i = 0; i < numBSPCubemaps; i++) + { + VectorSubtract(bspCubemaps[i].origin, mid, move); + d = DotProduct(move,move); + if (cmdist > d) + { + cmdist = d; + + if (bspCubemaps[i].cubesize <= 0) + cm = -1; //defer to default + else + cm = i; + } + } + return cm; +} + + +/* + CreateMapCubemaps() - spike + generates a list of map fogs + */ + +void CreateMapCubemaps( void ){ + int i; + entity_t *e; + + /* skip? */ + if ( nofog ) { + return; + } + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- CreateMapCubemaps ---\n" ); + + /* walk entities */ + for ( i = 1; i < numEntities; i++ ) + { + const char *size; + /* get entity */ + e = &entities[ i ]; + + if (strcmp(ValueForKey( e, "classname" ), "env_cubemap")) + continue; //not a cubemap, not interested in it. + + if (numBSPCubemaps == sizeof(bspCubemaps)/sizeof(bspCubemaps[0])) + { + Sys_FPrintf( SYS_WRN, "Too many env_cubemap entities, some will be ignored\n" ); + break; + } + sscanf( ValueForKey( e, "origin" ), "%f %f %f", &bspCubemaps[numBSPCubemaps].origin[ 0 ], &bspCubemaps[numBSPCubemaps].origin[ 1 ], &bspCubemaps[numBSPCubemaps].origin[ 2 ] ); + size = ValueForKey( e, "size" ); + if (*size) //0 or negative means defer to default (eg skybox or whatever) + sscanf( size, "%i", &bspCubemaps[numBSPCubemaps].cubesize ); + else + bspCubemaps[numBSPCubemaps].cubesize = 128; //a default size. + numBSPCubemaps++; + } + + //okay, we have our list, add it to the bsp state for eventual writing. + BSPX_CopyOut("ENVMAP", bspCubemaps, numBSPCubemaps*sizeof(bspCubemaps[0])); + + /* emit some stats */ + Sys_FPrintf( SYS_STD, "%9d cubemaps\n", numBSPCubemaps ); +} diff --git a/tools/vmap/game__null.h b/tools/vmap/game__null.h new file mode 100644 index 0000000..b93d392 --- /dev/null +++ b/tools/vmap/game__null.h @@ -0,0 +1,94 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME__NULL_H +#define GAME__NULL_H + + + +/* ------------------------------------------------------------------------------- + + content and surface flags + are in game_quake3.h + + ------------------------------------------------------------------------------- */ + + + +/* ------------------------------------------------------------------------------- + + game_t struct + + ------------------------------------------------------------------------------- */ + +{ + NULL, /* -game x */ + NULL, /* default base game data dir */ + NULL, /* unix home sub-dir */ + NULL, /* magic path word */ + NULL, /* shader directory */ + 0, /* max lightmapped surface verts */ + 0, /* max surface verts */ + 0, /* max surface indexes */ + qfalse, /* flares */ + NULL, /* default flare shader */ + qfalse, /* wolf lighting model? */ + 0, /* lightmap width/height */ + 0, /* lightmap gamma */ + qfalse, /* lightmap sRGB */ + qfalse, /* texture sRGB */ + qfalse, /* color sRGB */ + 0, /* lightmap exposure */ + 0, /* lightmap compensate */ + 0, /* lightgrid scale */ + 0, /* lightgrid ambient scale */ + qfalse, /* light angle attenuation uses half-lambert curve */ + qfalse, /* disable shader lightstyles hack */ + qfalse, /* keep light entities on bsp */ + 0, /* default patchMeta subdivisions tolerance */ + qfalse, /* patch casting enabled */ + qfalse, /* compile deluxemaps */ + 0, /* deluxemaps default mode */ + NULL, /* bsp file prefix */ + 0, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + NULL, /* bsp load function */ + NULL, /* bsp write function */ + + { + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif diff --git a/tools/vmap/game_fte.h b/tools/vmap/game_fte.h new file mode 100644 index 0000000..997f4b7 --- /dev/null +++ b/tools/vmap/game_fte.h @@ -0,0 +1,216 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_FTE_H +#define GAME_FTE_H + +/* game flags */ +#define Q_CONT_SOLID 1 /* an eye is never valid in a solid */ +#define Q_CONT_LAVA 8 +#define Q_CONT_SLIME 16 +#define Q_CONT_WATER 32 +#define Q_CONT_FOG 64 + +#define Q_CONT_AREAPORTAL 0x8000 + +#define Q_CONT_PLAYERCLIP 0x10000 +#define Q_CONT_MONSTERCLIP 0x20000 +#define Q_CONT_TELEPORTER 0x40000 +#define Q_CONT_JUMPPAD 0x80000 +#define Q_CONT_CLUSTERPORTAL 0x100000 +#define Q_CONT_DONOTENTER 0x200000 +#define Q_CONT_BOTCLIP 0x400000 + +#define Q_CONT_ORIGIN 0x1000000 /* removed before bsping an entity */ + +#define Q_CONT_BODY 0x2000000 /* should never be on a brush, only in game */ +#define Q_CONT_CORPSE 0x4000000 +#define Q_CONT_DETAIL 0x8000000 /* brushes not used for the bsp */ +#define Q_CONT_STRUCTURAL 0x10000000 /* brushes used for the bsp */ +#define Q_CONT_TRANSLUCENT 0x20000000 /* don't consume surface fragments inside */ +#define Q_CONT_TRIGGER 0x40000000 +#define Q_CONT_NODROP 0x80000000 /* don't leave bodies or items (death fog, lava) */ + +#define Q_SURF_NODAMAGE 0x1 /* never give falling damage */ +#define Q_SURF_SLICK 0x2 /* effects game physics */ +#define Q_SURF_SKY 0x4 /* lighting from environment map */ +#define Q_SURF_LADDER 0x8 +#define Q_SURF_NOIMPACT 0x10 /* don't make missile explosions */ +#define Q_SURF_NOMARKS 0x20 /* don't leave missile marks */ +#define Q_SURF_FLESH 0x40 /* make flesh sounds and effects */ +#define Q_SURF_NODRAW 0x80 /* don't generate a drawsurface at all */ +#define Q_SURF_HINT 0x100 /* make a primary bsp splitter */ +#define Q_SURF_SKIP 0x200 /* completely ignore, allowing non-closed brushes */ +#define Q_SURF_NOLIGHTMAP 0x400 /* surface doesn't need a lightmap */ +#define Q_SURF_POINTLIGHT 0x800 /* generate lighting info at vertexes */ +#define Q_SURF_METALSTEPS 0x1000 /* clanking footsteps */ +#define Q_SURF_NOSTEPS 0x2000 /* no footstep sounds */ +#define Q_SURF_NONSOLID 0x4000 /* don't collide against curves with this set */ +#define Q_SURF_LIGHTFILTER 0x8000 /* act as a light filter during q3map -light */ +#define Q_SURF_ALPHASHADOW 0x10000 /* do per-pixel light shadow casting in q3map */ +#define Q_SURF_NODLIGHT 0x20000 /* don't dlight even if solid (solid lava, skies) */ +#define Q_SURF_DUST 0x40000 /* leave a dust trail when walking on this surface */ + +/* ydnar flags */ +#define Q_SURF_VERTEXLIT ( Q_SURF_POINTLIGHT | Q_SURF_NOLIGHTMAP ) + + +#define FTE_CONT_LADDER 0x00004000 +#define FTE_SURF_MESHCOLLISION 0x80000000 + + + +/* ------------------------------------------------------------------------------- + + game_t struct + + ------------------------------------------------------------------------------- */ + +{ + "fte", /* -game x */ + "wastes", /* default base game data dir */ + ".fte", /* unix home sub-dir */ + "quake", /* magic path word */ + "scripts", /* shader directory */ + 65535, /* max lightmapped surface verts */ + 65535, /* max surface verts */ + 1048575, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 512, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + qfalse, /* lightmap sRGB */ + qtrue, /* texture sRGB */ + qfalse, /* color sRGB */ + 0.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + 1.0f, /* lightgrid scale */ + 1.0f, /* lightgrid ambient scale */ + qtrue, /* light angle attenuation uses half-lambert curve */ + qtrue, /* disable shader lightstyles hack */ + qtrue, /* keep light entities on bsp */ + 8, /* default patchMeta subdivisions tolerance */ + qtrue, /* patch casting enabled */ + qtrue, /* compile deluxemaps */ + 1, /* deluxemaps default mode */ + "FBSP", /* bsp file prefix */ + 1, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadRBSPFile, /* bsp load function */ + WriteRBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", Q_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", Q_CONT_ORIGIN, Q_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", Q_CONT_AREAPORTAL, Q_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", Q_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", Q_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", Q_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, Q_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, Q_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, Q_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, Q_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, Q_CONT_SOLID, Q_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", Q_CONT_TRIGGER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", Q_CONT_WATER, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", Q_CONT_SLIME, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", Q_CONT_LAVA, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", Q_CONT_PLAYERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", Q_CONT_MONSTERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", Q_CONT_NODROP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", Q_CONT_CLUSTERPORTAL, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", Q_CONT_DONOTENTER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", Q_CONT_BOTCLIP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", Q_CONT_FOG, Q_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, Q_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, Q_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, Q_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, Q_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, Q_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, Q_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, Q_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, Q_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, Q_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, Q_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, Q_SURF_DUST, 0, 0, 0 }, + + /* nodraw2 is seen by the engine but NOT handled by the compiler tools, and thus never stripped */ + { "nodraw2", 0, 0, Q_SURF_NODRAW, 0, 0, 0 }, + /* q2/hlish-style ladder volumes */ + { "laddervolume", FTE_CONT_LADDER, Q_CONT_SOLID, 0, 0, 0, 0 }, + /* trisoup uses mesh collisions, instead of being non-solid */ + { "meshcollision", 0, 0, FTE_SURF_MESHCOLLISION, 0, 0, 0 }, + + /* these are just here to avoid warnings, parsed by the engine */ + { "hasdiffuse", 0, 0, 0, 0, 0, 0 }, + { "hasnormalmap", 0, 0, 0, 0, 0, 0 }, + { "hasgloss", 0, 0, 0, 0, 0, 0 }, + { "hasfullbright", 0, 0, 0, 0, 0, 0 }, + { "haspaletted", 0, 0, 0, 0, 0, 0 }, + { "hastop", 0, 0, 0, 0, 0, 0 }, + { "hasbottom", 0, 0, 0, 0, 0, 0 }, + { "hastopbottom", 0, 0, 0, 0, 0, 0 }, + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif diff --git a/tools/vmap/help.c b/tools/vmap/help.c new file mode 100644 index 0000000..fa259cf --- /dev/null +++ b/tools/vmap/help.c @@ -0,0 +1,424 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* dependencies */ +#include "vmap.h" + + + +struct HelpOption +{ + const char* name; + const char* description; +}; + +void HelpOptions(const char* group_name, int indentation, int width, struct HelpOption* options, int count) +{ + indentation *= 2; + char* indent = malloc(indentation+1); + memset(indent, ' ', indentation); + indent[indentation] = 0; + printf("%s%s:\n", indent, group_name); + indentation += 2; + indent = realloc(indent, indentation+1); + memset(indent, ' ', indentation); + indent[indentation] = 0; + + int i; + for ( i = 0; i < count; i++ ) + { + int printed = printf("%s%-24s ", indent, options[i].name); + int descsz = strlen(options[i].description); + int j = 0; + while ( j < descsz && descsz-j > width - printed ) + { + if ( j != 0 ) + printf("%s%26c",indent,' '); + int fragment = width - printed; + while ( fragment > 0 && options[i].description[j+fragment-1] != ' ') + fragment--; + j += fwrite(options[i].description+j, sizeof(char), fragment, stdout); + putchar('\n'); + printed = indentation+26; + } + if ( j == 0 ) + { + printf("%s\n",options[i].description+j); + } + else if ( j < descsz ) + { + printf("%s%26c%s\n",indent,' ',options[i].description+j); + } + } + + putchar('\n'); + + free(indent); +} + +void HelpBsp() +{ + struct HelpOption bsp[] = { + {"-bsp ", "Switch that enters this stage"}, + {"-altsplit", "Alternate BSP tree splitting weights (should give more fps)"}, + {"-bspfile ", "BSP file to write"}, + {"-celshader ", "Sets a global cel shader name"}, + {"-custinfoparms", "Read scripts/custinfoparms.txt"}, + {"-debuginset", "Push all triangle vertexes towards the triangle center"}, + {"-debugportals", "Make BSP portals visible in the map"}, + {"-debugsurfaces", "Color the vertexes according to the index of the surface"}, + {"-deep", "Use detail brushes in the BSP tree, but at lowest priority (should give more fps)"}, + {"-de ", "Distance epsilon for plane snapping etc."}, + {"-fakemap", "Write fakemap.map containing all world brushes"}, + {"-flares", "Turn on support for flares (TEST?)"}, + {"-flat", "Enable flat shading (good for combining with -celshader)"}, + {"-fulldetail", "Treat detail brushes as structural ones"}, + {"-leaktest", "Continue even if a leak was found"}, + {"-linfile ", "Line file to write"}, + {"-meta", "Combine adjacent triangles of the same texture to surfaces (ALWAYS USE THIS)"}, + {"-minsamplesize ", "Sets minimum lightmap resolution in luxels/qu"}, + {"-mi ", "Sets the maximum number of indexes per surface"}, + {"-mv ", "Sets the maximum number of vertices of a lightmapped surface"}, + {"-ne ", "Normal epsilon for plane snapping etc."}, + {"-nocurves", "Turn off support for patches"}, + {"-nodetail", "Leave out detail brushes"}, + {"-noflares", "Turn off support for flares"}, + {"-nofog", "Turn off support for fog volumes"}, + {"-nohint", "Turn off support for hint brushes"}, + {"-nosubdivide", "Turn off support for `q3map_tessSize` (breaks water vertex deforms)"}, + {"-notjunc", "Do not fix T-junctions (causes cracks between triangles, do not use)"}, + {"-nowater", "Turn off support for water, slime or lava (Stef, this is for you)"}, + {"-np ", "Force all surfaces to be nonplanar with a given shade angle"}, + {"-onlyents", "Only update entities in the BSP"}, + {"-patchmeta", "Turn patches into triangle meshes for display"}, + {"-prtfile ", "Portal file to write"}, + {"-rename", "Append suffix to miscmodel shaders (needed for SoF2)"}, + {"-samplesize ", "Sets default lightmap resolution in luxels/qu"}, + {"-skyfix", "Turn sky box into six surfaces to work around ATI problems"}, + {"-snap ", "Snap brush bevel planes to the given number of units"}, + {"-srffile ", "Surface file to write"}, + {"-tempname ", "Read the MAP file from the given file name"}, + {"-texrange ", "Limit per-surface texture range to the given number of units, and subdivide surfaces like with `q3map_tessSize` if this is not met"}, + {"-tmpout", "Write the BSP file to /tmp"}, + {"-verboseentities", "Enable `-v` only for map entities, not for the world"}, + }; + HelpOptions("BSP Stage", 0, 80, bsp, sizeof(bsp)/sizeof(struct HelpOption)); +} + +void HelpVis() +{ + struct HelpOption vis[] = { + {"-vis ", "Switch that enters this stage"}, + {"-fast", "Very fast and crude vis calculation"}, + {"-hint", "Merge all but hint portals"}, + {"-mergeportals", "The less crude half of `-merge`, makes vis sometimes much faster but doesn't hurt fps usually"}, + {"-merge", "Faster but still okay vis calculation"}, + {"-nopassage", "Just use PortalFlow vis (usually less fps)"}, + {"-nosort", "Do not sort the portals before calculating vis (usually slower)"}, + {"-passageOnly", "Just use PassageFlow vis (usually less fps)"}, + {"-prtfile ", "Portal file to read"}, + {"-saveprt", "Keep the Portal file after running vis (so you can run vis again)"}, + {"-tmpin", "Use /tmp folder for input"}, + {"-tmpout", "Use /tmp folder for output"}, + }; + HelpOptions("VIS Stage", 0, 80, vis, sizeof(vis)/sizeof(struct HelpOption)); +} + +void HelpLight() +{ + struct HelpOption light[] = { + {"-light ", "Switch that enters this stage"}, + {"-vlight ", "Deprecated alias for `-light -fast` ... filename.map"}, + {"-approx ", "Vertex light approximation tolerance (never use in conjunction with deluxemapping)"}, + {"-areascale ", "Scaling factor for area lights (surfacelight)"}, + {"-border", "Add a red border to lightmaps for debugging"}, + {"-bouncegrid", "Also compute radiosity on the light grid"}, + {"-bounceonly", "Only compute radiosity"}, + {"-bouncescale ", "Scaling factor for radiosity"}, + {"-bounce ", "Number of bounces for radiosity"}, + {"-bspfile ", "BSP file to write"}, + {"-cheapgrid", "Use `-cheap` style lighting for radiosity"}, + {"-cheap", "Abort vertex light calculations when white is reached"}, + {"-compensate ", "Lightmap compensate (darkening factor applied after everything else)"}, + {"-cpma", "CPMA vertex lighting mode"}, + {"-custinfoparms", "Read scripts/custinfoparms.txt"}, + {"-dark", "Darken lightmap seams"}, + {"-debugaxis", "Color the lightmaps according to the lightmap axis"}, + {"-debugcluster", "Color the lightmaps according to the index of the cluster"}, + {"-debugdeluxe", "Show deluxemaps on the lightmap"}, + {"-debugnormals", "Color the lightmaps according to the direction of the surface normal"}, + {"-debugorigin", "Color the lightmaps according to the origin of the luxels"}, + {"-debugsurfaces, -debugsurface", "Color the lightmaps according to the index of the surface"}, + {"-debugunused", "This option does nothing"}, + {"-debug", "Mark the lightmaps according to the cluster: unmapped clusters get yellow, occluded ones get pink, flooded ones get blue overlay color, otherwise red"}, + {"-deluxemode 0", "Use modelspace deluxemaps (DarkPlaces)"}, + {"-deluxemode 1", "Use tangentspace deluxemaps"}, + {"-deluxe, -deluxemap", "Enable deluxemapping (light direction maps)"}, + {"-dirtdebug, -debugdirt", "Store the dirtmaps as lightmaps for debugging"}, + {"-dirtdepth", "Dirtmapping depth"}, + {"-dirtgain", "Dirtmapping exponent"}, + {"-dirtmode 0", "Ordered direction dirtmapping"}, + {"-dirtmode 1", "Randomized direction dirtmapping"}, + {"-dirtscale", "Dirtmapping scaling factor"}, + {"-dirty", "Enable dirtmapping"}, + {"-dump", "Dump radiosity from `-bounce` into numbered MAP file prefabs"}, + {"-export", "Export lightmaps when compile finished (like `-export` mode)"}, + {"-exposure ", "Lightmap exposure to better support overbright spots"}, + {"-external", "Force external lightmaps even if at size of internal lightmaps"}, + {"-extravisnudge", "Broken feature to nudge the luxel origin to a better vis cluster"}, + {"-extrawide", "Deprecated alias for `-super 2 -filter`"}, + {"-extra", "Deprecated alias for `-super 2`"}, + {"-fastallocate", "Use `-fastallocate` to trade lightmap size against allocation time (useful with hi res lightmaps on large maps: reduce allocation time from days to minutes for only some extra bytes)"}, + {"-fastbounce", "Use `-fast` style lighting for radiosity"}, + {"-faster", "Use a faster falloff curve for lighting; also implies `-fast`"}, + {"-fastgrid", "Use `-fast` style lighting for the light grid"}, + {"-fast", "Ignore tiny light contributions"}, + {"-filter", "Lightmap filtering"}, + {"-floodlight", "Enable floodlight (zero-effort somewhat decent lighting)"}, + {"-gamma ", "Lightmap gamma"}, + {"-gridambientscale ", "Scaling factor for the light grid ambient components only"}, + {"-gridscale ", "Scaling factor for the light grid only"}, + {"-keeplights", "Keep light entities in the BSP file after compile"}, + {"-lightmapdir ", "Directory to store external lightmaps (default: same as map name without extension)"}, + {"-lightmapsearchblocksize ", "Restrict lightmap search to block size "}, + {"-lightmapsearchpower ", "Optimize for lightmap merge power "}, + {"-lightmapsize ", "Size of lightmaps to generate (must be a power of two)"}, + {"-lightsubdiv ", "Size of light emitting shader subdivision"}, + {"-lomem", "Low memory but slower lighting mode"}, + {"-lowquality", "Low quality floodlight (appears to currently break floodlight)"}, + {"-minsamplesize ", "Sets minimum lightmap resolution in luxels/qu"}, + {"-nocollapse", "Do not collapse identical lightmaps"}, + {"-nodeluxe, -nodeluxemap", "Disable deluxemapping"}, + {"-nogrid", "Disable grid light calculation (makes all entities fullbright)"}, + {"-nolightmapsearch", "Do not optimize lightmap packing for GPU memory usage (as doing so costs fps)"}, + {"-normalmap", "Color the lightmaps according to the direction of the surface normal (TODO is this identical to `-debugnormals`?)"}, + {"-nostyle, -nostyles", "Disable support for light styles"}, + {"-nosurf", "Disable tracing against surfaces (only uses BSP nodes then)"}, + {"-notrace", "Disable shadow occlusion"}, + {"-novertex", "Disable vertex lighting"}, + {"-patchshadows", "Cast shadows from patches"}, + {"-pointscale ", "Scaling factor for point lights (light entities)"}, + {"-q3", "Use nonlinear falloff curve by default (like Q3A)"}, + {"-samplescale ", "Scales all lightmap resolutions"}, + {"-samplesize ", "Sets default lightmap resolution in luxels/qu"}, + {"-samples ", "Adaptive supersampling quality"}, + {"-scale ", "Scaling factor for all light types"}, + {"-shadeangle ", "Angle for phong shading"}, + {"-shade", "Enable phong shading at default shade angle"}, + {"-skyscale ", "Scaling factor for sky and sun light"}, + {"-smooth", "Deprecated alias for `-samples 2`"}, + {"-srffile ", "Surface file to read"}, + {"-style, -styles", "Enable support for light styles"}, + {"-sunonly", "Only compute sun light"}, + {"-super ", "Ordered grid supersampling quality"}, + {"-thresh ", "Triangle subdivision threshold"}, + {"-trianglecheck", "Broken check that should ensure luxels apply to the right triangle"}, + {"-trisoup", "Convert brush faces to triangle soup"}, + {"-wolf", "Use linear falloff curve by default (like W:ET)"}, + }; + + HelpOptions("Light Stage", 0, 80, light, sizeof(light)/sizeof(struct HelpOption)); +} + +void HelpAnalyze() +{ + struct HelpOption analyze[] = { + {"-analyze ", "Switch that enters this mode"}, + {"-lumpswap", "Swap byte order in the lumps"}, + }; + + HelpOptions("Analyzing BSP-like file structure", 0, 80, analyze, sizeof(analyze)/sizeof(struct HelpOption)); +} + +void HelpScale() +{ + struct HelpOption scale[] = { + {"-scale ", "Scale uniformly"}, + {"-scale ", "Scale non-uniformly"}, + {"-scale -tex ", "Scale uniformly without texture lock"}, + {"-scale -tex ", "Scale non-uniformly without texture lock"}, + }; + HelpOptions("Scaling", 0, 80, scale, sizeof(scale)/sizeof(struct HelpOption)); +} + +void HelpConvert() +{ + struct HelpOption convert[] = { + {"-convert ", "Switch that enters this mode"}, + {"-de ", "Distance epsilon for the conversion"}, + {"-format ", "Select the converter (available: map, ase, or game names)"}, + {"-ne ", "Normal epsilon for the conversion"}, + {"-shadersasbitmap", "(only for ase) use the shader names as \\*BITMAP key so they work as prefabs"}, + }; + + HelpOptions("Converting & Decompiling", 0, 80, convert, sizeof(convert)/sizeof(struct HelpOption)); +} + +void HelpExport() +{ + struct HelpOption exportl[] = { + {"-export ", "Copies lightmaps from the BSP to `filename/lightmap_0000.tga` ff"} + }; + + HelpOptions("Exporting lightmaps", 0, 80, exportl, sizeof(exportl)/sizeof(struct HelpOption)); +} + +void HelpExportEnts() +{ + struct HelpOption exportents[] = { + {"-exportents ", "Exports the entities to a text file (.ent)"}, + }; + HelpOptions("ExportEnts Stage", 0, 80, exportents, sizeof(exportents)/sizeof(struct HelpOption)); +} + +void HelpFixaas() +{ + struct HelpOption fixaas[] = { + {"-fixaas ", "Switch that enters this mode"}, + }; + + HelpOptions("Fixing AAS checksum", 0, 80, fixaas, sizeof(fixaas)/sizeof(struct HelpOption)); +} + +void HelpInfo() +{ + struct HelpOption info[] = { + {"-info ", "Switch that enters this mode"}, + }; + + HelpOptions("Get info about BSP file", 0, 80, info, sizeof(info)/sizeof(struct HelpOption)); +} + +void HelpImport() +{ + struct HelpOption import[] = { + {"-import ", "Copies lightmaps from `filename/lightmap_0000.tga` ff into the BSP"}, + }; + + HelpOptions("Importing lightmaps", 0, 80, import, sizeof(import)/sizeof(struct HelpOption)); +} + +void HelpMinimap() +{ + struct HelpOption minimap[] = { + {"-minimap ", "Creates a minimap of the BSP, by default writes to `../gfx/filename_mini.tga`"}, + {"-black", "Write the minimap as a black-on-transparency RGBA32 image"}, + {"-boost ", "Sets the contrast boost value (higher values make a brighter image); contrast boost is somewhat similar to gamma, but continuous even at zero"}, + {"-border ", "Sets the amount of border pixels relative to the total image size"}, + {"-gray", "Write the minimap as a white-on-black GRAY8 image"}, + {"-keepaspect", "Ensure the aspect ratio is kept (the minimap is then letterboxed to keep aspect)"}, + {"-minmax ", "Forces specific map dimensions (note: the minimap actually uses these dimensions, scaled to the target size while keeping aspect with centering, and 1/64 of border appended to all sides)"}, + {"-nokeepaspect", "Do not ensure the aspect ratio is kept (makes it easier to use the image in your code, but looks bad together with sharpening)"}, + {"-o ", "Sets the output file name"}, + {"-random ", "Sets the randomized supersampling count (cannot be combined with `-samples`)"}, + {"-samples ", "Sets the ordered supersampling count (cannot be combined with `-random`)"}, + {"-sharpen ", "Sets the sharpening coefficient"}, + {"-size ", "Sets the width and height of the output image"}, + {"-white", "Write the minimap as a white-on-transparency RGBA32 image"}, + }; + + HelpOptions("MiniMap", 0, 80, minimap, sizeof(minimap)/sizeof(struct HelpOption)); +} + +void HelpCommon() +{ + struct HelpOption common[] = { + {"-connect

", "Talk to a WorldSpawn instance using a specific XML based protocol"}, + {"-force", "Allow reading some broken/unsupported BSP files e.g. when decompiling, may also crash"}, + {"-fs_basepath ", "Sets the given path as main directory of the game (can be used more than once to look in multiple paths)"}, + {"-fs_game ", "Sets a different game directory name (default for Q3A: baseq3, can be used more than once)"}, + {"-fs_homebase ", "Specifies where the user home directory name is on Linux (default for Q3A: .q3a)"}, + {"-fs_homepath ", "Sets the given path as home directory name"}, + {"-fs_nobasepath", "Do not load base paths in VFS, imply -fs_nomagicpath"}, + {"-fs_nomagicpath", "Do not try to guess base path magically"}, + {"-fs_nohomepath", "Do not load home path in VFS"}, + {"-fs_pakpath ", "Specify a package directory (can be used more than once to look in multiple paths)"}, + {"-game ", "Load settings for the given game (default: quake3)"}, + {"-subdivisions ", "multiplier for patch subdivisions quality"}, + {"-threads ", "number of threads to use"}, + {"-v", "Verbose mode"} + }; + + HelpOptions("Common Options", 0, 80, common, sizeof(common)/sizeof(struct HelpOption)); + +} + +void HelpMain(const char* arg) +{ + printf("Usage: q3map2 [stage] [common options...] [stage options...] [stage source file]\n"); + printf(" q3map2 -help [stage]\n\n"); + + HelpCommon(); + + struct HelpOption stages[] = { + {"-bsp", "BSP Stage"}, + {"-vis", "VIS Stage"}, + {"-light", "Light Stage"}, + {"-analyze", "Analyzing BSP-like file structure"}, + {"-scale", "Scaling"}, + {"-convert", "Converting & Decompiling"}, + {"-export", "Exporting lightmaps"}, + {"-exportents", "Exporting entities"}, + {"-fixaas", "Fixing AAS checksum"}, + {"-info", "Get info about BSP file"}, + {"-import", "Importing lightmaps"}, + {"-minimap", "MiniMap"}, + }; + void(*help_funcs[])() = { + HelpBsp, + HelpVis, + HelpLight, + HelpAnalyze, + HelpScale, + HelpConvert, + HelpExport, + HelpExportEnts, + HelpFixaas, + HelpInfo, + HelpImport, + HelpMinimap, + }; + + if ( arg && strlen(arg) > 0 ) + { + if ( arg[0] == '-' ) + arg++; + + unsigned i; + for ( i = 0; i < sizeof(stages)/sizeof(struct HelpOption); i++ ) + if ( strcmp(arg, stages[i].name+1) == 0 ) + { + help_funcs[i](); + return; + } + } + + HelpOptions("Stages", 0, 80, stages, sizeof(stages)/sizeof(struct HelpOption)); +} diff --git a/tools/vmap/image.c b/tools/vmap/image.c new file mode 100644 index 0000000..8e0cb4e --- /dev/null +++ b/tools/vmap/image.c @@ -0,0 +1,491 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define IMAGE_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* ------------------------------------------------------------------------------- + + this file contains image pool management with reference counting. note: it isn't + reentrant, so only call it from init/shutdown code or wrap calls in a mutex + + ------------------------------------------------------------------------------- */ + +/* + LoadDDSBuffer() + loads a dxtc (1, 3, 5) dds buffer into a valid rgba image + */ + +static void LoadDDSBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ){ + int w, h; + ddsPF_t pf; + + + /* dummy check */ + if ( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) { + return; + } + + /* null out */ + *pixels = 0; + *width = 0; + *height = 0; + + /* get dds info */ + if ( DDSGetInfo( (ddsBuffer_t*) buffer, &w, &h, &pf ) ) { + Sys_FPrintf( SYS_WRN, "WARNING: Invalid DDS texture\n" ); + return; + } + + /* only certain types of dds textures are supported */ + if ( pf != DDS_PF_ARGB8888 && pf != DDS_PF_DXT1 && pf != DDS_PF_DXT3 && pf != DDS_PF_DXT5 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Only DDS texture formats ARGB8888, DXT1, DXT3, and DXT5 are supported (%d)\n", pf ); + return; + } + + /* create image pixel buffer */ + *width = w; + *height = h; + *pixels = safe_malloc( w * h * 4 ); + + /* decompress the dds texture */ + DDSDecompress( (ddsBuffer_t*) buffer, *pixels ); +} + + + +/* + PNGReadData() + callback function for libpng to read from a memory buffer + note: this function is a total hack, as it reads/writes the png struct directly! + */ + +typedef struct pngBuffer_s +{ + byte *buffer; + png_size_t size, offset; +} pngBuffer_t; + +void PNGReadData( png_struct *png, png_byte *buffer, png_size_t size ){ + pngBuffer_t *pb = (pngBuffer_t*) png_get_io_ptr( png ); + + + if ( ( pb->offset + size ) > pb->size ) { + size = ( pb->size - pb->offset ); + } + memcpy( buffer, &pb->buffer[ pb->offset ], size ); + pb->offset += size; + //% Sys_Printf( "Copying %d bytes from 0x%08X to 0x%08X (offset: %d of %d)\n", size, &pb->buffer[ pb->offset ], buffer, pb->offset, pb->size ); +} + + + +/* + LoadPNGBuffer() + loads a png file buffer into a valid rgba image + */ + +static void LoadPNGBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ){ + png_struct *png; + png_info *info, *end; + pngBuffer_t pb; + int bitDepth, colorType; + png_uint_32 w, h, i; + byte **rowPointers; + + /* dummy check */ + if ( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) { + return; + } + + /* null out */ + *pixels = 0; + *width = 0; + *height = 0; + + /* determine if this is a png file */ + if ( png_sig_cmp( buffer, 0, 8 ) != 0 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Invalid PNG file\n" ); + return; + } + + /* create png structs */ + png = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); + if ( png == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Unable to create PNG read struct\n" ); + return; + } + + info = png_create_info_struct( png ); + if ( info == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Unable to create PNG info struct\n" ); + png_destroy_read_struct( &png, NULL, NULL ); + return; + } + + end = png_create_info_struct( png ); + if ( end == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Unable to create PNG end info struct\n" ); + png_destroy_read_struct( &png, &info, NULL ); + return; + } + + /* set read callback */ + pb.buffer = buffer; + pb.size = size; + pb.offset = 0; + png_set_read_fn( png, &pb, PNGReadData ); + + /* set error longjmp */ + if ( setjmp( png_jmpbuf(png) ) ) { + Sys_FPrintf( SYS_WRN, "WARNING: An error occurred reading PNG image\n" ); + png_destroy_read_struct( &png, &info, &end ); + return; + } + + /* fixme: add proper i/o stuff here */ + + /* read png info */ + png_read_info( png, info ); + + /* read image header chunk */ + png_get_IHDR( png, info, + &w, &h, &bitDepth, &colorType, NULL, NULL, NULL ); + + /* the following will probably bork on certain types of png images, but hey... */ + + /* force indexed/gray/trans chunk to rgb */ + if ( ( colorType == PNG_COLOR_TYPE_PALETTE && bitDepth <= 8 ) || + ( colorType == PNG_COLOR_TYPE_GRAY && bitDepth <= 8 ) || + png_get_valid( png, info, PNG_INFO_tRNS ) ) { + png_set_expand( png ); + } + + /* strip 16bpc -> 8bpc */ + if ( bitDepth == 16 ) { + png_set_strip_16( png ); + } + + /* pad rgb to rgba */ + if ( bitDepth == 8 && colorType == PNG_COLOR_TYPE_RGB ) { + png_set_filler( png, 255, PNG_FILLER_AFTER ); + } + + /* create image pixel buffer */ + *width = w; + *height = h; + *pixels = safe_malloc( w * h * 4 ); + + /* create row pointers */ + rowPointers = safe_malloc( h * sizeof( byte* ) ); + for ( i = 0; i < h; i++ ) + rowPointers[ i ] = *pixels + ( i * w * 4 ); + + /* read the png */ + png_read_image( png, rowPointers ); + + /* clean up */ + free( rowPointers ); + png_destroy_read_struct( &png, &info, &end ); + +} + + + +/* + ImageInit() + implicitly called by every function to set up image list + */ + +static void ImageInit( void ){ + int i; + + + if ( numImages <= 0 ) { + /* clear images (fixme: this could theoretically leak) */ + memset( images, 0, sizeof( images ) ); + + /* generate *bogus image */ + images[ 0 ].name = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 ); + strcpy( images[ 0 ].name, DEFAULT_IMAGE ); + images[ 0 ].filename = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 ); + strcpy( images[ 0 ].filename, DEFAULT_IMAGE ); + images[ 0 ].width = 64; + images[ 0 ].height = 64; + images[ 0 ].refCount = 1; + images[ 0 ].pixels = safe_malloc( 64 * 64 * 4 ); + for ( i = 0; i < ( 64 * 64 * 4 ); i++ ) + images[ 0 ].pixels[ i ] = 255; + } +} + + + +/* + ImageFree() + frees an rgba image + */ + +void ImageFree( image_t *image ){ + /* dummy check */ + if ( image == NULL ) { + return; + } + + /* decrement refcount */ + image->refCount--; + + /* free? */ + if ( image->refCount <= 0 ) { + if ( image->name != NULL ) { + free( image->name ); + } + image->name = NULL; + if ( image->filename != NULL ) { + free( image->filename ); + } + image->filename = NULL; + free( image->pixels ); + image->width = 0; + image->height = 0; + numImages--; + } +} + + + +/* + ImageFind() + finds an existing rgba image and returns a pointer to the image_t struct or NULL if not found + */ + +image_t *ImageFind( const char *filename ){ + int i; + char name[ 1024 ]; + + + /* init */ + ImageInit(); + + /* dummy check */ + if ( filename == NULL || filename[ 0 ] == '\0' ) { + return NULL; + } + + /* strip file extension off name */ + strcpy( name, filename ); + StripExtension( name ); + + /* search list */ + for ( i = 0; i < MAX_IMAGES; i++ ) + { + if ( images[ i ].name != NULL && !strcmp( name, images[ i ].name ) ) { + return &images[ i ]; + } + } + + /* no matching image found */ + return NULL; +} + + + +/* + ImageLoad() + loads an rgba image and returns a pointer to the image_t struct or NULL if not found + */ + +image_t *ImageLoad( const char *filename ){ + int i; + image_t *image; + char name[ 1024 ]; + int size; + byte *buffer = NULL; + qboolean alphaHack = qfalse; + + + /* init */ + ImageInit(); + + /* dummy check */ + if ( filename == NULL || filename[ 0 ] == '\0' ) { + return NULL; + } + + /* strip file extension off name */ + strcpy( name, filename ); + StripExtension( name ); + + /* try to find existing image */ + image = ImageFind( name ); + if ( image != NULL ) { + image->refCount++; + return image; + } + + /* none found, so find first non-null image */ + image = NULL; + for ( i = 0; i < MAX_IMAGES; i++ ) + { + if ( images[ i ].name == NULL ) { + image = &images[ i ]; + break; + } + } + + /* too many images? */ + if ( image == NULL ) { + Error( "MAX_IMAGES (%d) exceeded, there are too many image files referenced by the map.", MAX_IMAGES ); + } + + /* set it up */ + image->name = safe_malloc( strlen( name ) + 1 ); + strcpy( image->name, name ); + + /* attempt to load tga */ + StripExtension( name ); + strcat( name, ".tga" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if ( size > 0 ) { + LoadTGABuffer( buffer, buffer + size, &image->pixels, &image->width, &image->height ); + } + else + { + /* attempt to load png */ + StripExtension( name ); + strcat( name, ".png" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if ( size > 0 ) { + LoadPNGBuffer( buffer, size, &image->pixels, &image->width, &image->height ); + } + else + { + /* attempt to load jpg */ + StripExtension( name ); + strcat( name, ".jpg" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if ( size > 0 ) { + if ( LoadJPGBuff( buffer, size, &image->pixels, &image->width, &image->height ) == -1 && image->pixels != NULL ) { + // On error, LoadJPGBuff might store a pointer to the error message in image->pixels + Sys_FPrintf( SYS_WRN, "WARNING: LoadJPGBuff: %s\n", (unsigned char*) image->pixels ); + } + alphaHack = qtrue; + } + else + { + /* attempt to load dds */ + StripExtension( name ); + strcat( name, ".dds" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if ( size > 0 ) { + LoadDDSBuffer( buffer, size, &image->pixels, &image->width, &image->height ); + + /* debug code */ + #if 0 + { + ddsPF_t pf; + DDSGetInfo( (ddsBuffer_t*) buffer, NULL, NULL, &pf ); + Sys_Printf( "pf = %d\n", pf ); + if ( image->width > 0 ) { + StripExtension( name ); + strcat( name, "_converted.tga" ); + WriteTGA( "C:\\games\\quake3\\baseq3\\textures\\rad\\dds_converted.tga", image->pixels, image->width, image->height ); + } + } + #endif + } + else + { + /* attempt to load ktx */ + StripExtension( name ); + strcat( name, ".ktx" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if ( size > 0 ) { + LoadKTXBufferFirstImage( buffer, size, &image->pixels, &image->width, &image->height ); + } + } + } + } + } + + /* free file buffer */ + free( buffer ); + + /* make sure everything's kosher */ + if ( size <= 0 || image->width <= 0 || image->height <= 0 || image->pixels == NULL ) { + //% Sys_Printf( "size = %d width = %d height = %d pixels = 0x%08x (%s)\n", + //% size, image->width, image->height, image->pixels, name ); + free( image->name ); + image->name = NULL; + return NULL; + } + + /* set filename */ + image->filename = safe_malloc( strlen( name ) + 1 ); + strcpy( image->filename, name ); + + /* set count */ + image->refCount = 1; + numImages++; + + if ( alphaHack ) { + StripExtension( name ); + strcat( name, "_alpha.jpg" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if ( size > 0 ) { + unsigned char *pixels; + int width, height; + if ( LoadJPGBuff( buffer, size, &pixels, &width, &height ) == -1 ) { + if (pixels) { + // On error, LoadJPGBuff might store a pointer to the error message in pixels + Sys_FPrintf( SYS_WRN, "WARNING: LoadJPGBuff %s %s\n", name, (unsigned char*) pixels ); + } + } else { + if ( width == image->width && height == image->height ) { + int i; + for ( i = 0; i < width * height; ++i ) + image->pixels[4 * i + 3] = pixels[4 * i + 2]; // copy alpha from blue channel + } + free( pixels ); + } + free( buffer ); + } + } + + /* return the image */ + return image; +} diff --git a/tools/vmap/leakfile.c b/tools/vmap/leakfile.c new file mode 100644 index 0000000..b1a5bc0 --- /dev/null +++ b/tools/vmap/leakfile.c @@ -0,0 +1,124 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LEAKFILE_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + ============================================================================== + + LEAK FILE GENERATION + + Save out name.lin for qe3 to read + ============================================================================== + */ + + +/* + ============= + LeakFile + + Finds the shortest possible chain of portals + that leads from the outside leaf to a specifically + occupied leaf + + TTimo: builds a polyline xml node + ============= + */ +xmlNodePtr LeakFile( tree_t *tree, const char *filename ){ + vec3_t mid; + FILE *linefile; + node_t *node; + int count; + xmlNodePtr xml_node, point; + + if ( !tree->outside_node.occupied ) { + return NULL; + } + + Sys_FPrintf( SYS_VRB,"--- LeakFile ---\n" ); + + // + // write the points to the file + // + linefile = fopen( filename, "w" ); + if ( !linefile ) { + Error( "Couldn't open %s\n", filename ); + } + + xml_node = xmlNewNode( NULL, (xmlChar*)"polyline" ); + + count = 0; + node = &tree->outside_node; + while ( node->occupied > 1 ) + { + int next; + portal_t *p, *nextportal = NULL; + node_t *nextnode = NULL; + int s; + + // find the best portal exit + next = node->occupied; + for ( p = node->portals ; p ; p = p->next[!s] ) + { + s = ( p->nodes[0] == node ); + if ( p->nodes[s]->occupied + && p->nodes[s]->occupied < next ) { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter( nextportal->winding, mid ); + fprintf( linefile, "%f %f %f\n", mid[0], mid[1], mid[2] ); + point = xml_NodeForVec( mid ); + xmlAddChild( xml_node, point ); + count++; + } + // add the occupant center + GetVectorForKey( node->occupant, "origin", mid ); + + fprintf( linefile, "%f %f %f\n", mid[0], mid[1], mid[2] ); + point = xml_NodeForVec( mid ); + xmlAddChild( xml_node, point ); + Sys_FPrintf( SYS_VRB, "%9d point linefile\n", count + 1 ); + + fclose( linefile ); + + return xml_node; +} diff --git a/tools/vmap/light.c b/tools/vmap/light.c new file mode 100644 index 0000000..843c390 --- /dev/null +++ b/tools/vmap/light.c @@ -0,0 +1,3155 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + CreateSunLight() - ydnar + this creates a sun light + */ + +static void CreateSunLight( sun_t *sun ){ + int i; + float photons, d, angle, elevation, da, de; + vec3_t direction; + light_t *light; + + + /* dummy check */ + if ( sun == NULL ) { + return; + } + + /* fixup */ + if ( sun->numSamples < 1 ) { + sun->numSamples = 1; + } + + /* set photons */ + photons = sun->photons / sun->numSamples; + + /* create the right number of suns */ + for ( i = 0; i < sun->numSamples; i++ ) + { + /* calculate sun direction */ + if ( i == 0 ) { + VectorCopy( sun->direction, direction ); + } + else + { + /* + sun->direction[ 0 ] = cos( angle ) * cos( elevation ); + sun->direction[ 1 ] = sin( angle ) * cos( elevation ); + sun->direction[ 2 ] = sin( elevation ); + + xz_dist = sqrt( x*x + z*z ) + latitude = atan2( xz_dist, y ) * RADIANS + longitude = atan2( x, z ) * RADIANS + */ + + d = sqrt( sun->direction[ 0 ] * sun->direction[ 0 ] + sun->direction[ 1 ] * sun->direction[ 1 ] ); + angle = atan2( sun->direction[ 1 ], sun->direction[ 0 ] ); + elevation = atan2( sun->direction[ 2 ], d ); + + /* jitter the angles (loop to keep random sample within sun->deviance steridians) */ + do + { + da = ( Random() * 2.0f - 1.0f ) * sun->deviance; + de = ( Random() * 2.0f - 1.0f ) * sun->deviance; + } + while ( ( da * da + de * de ) > ( sun->deviance * sun->deviance ) ); + angle += da; + elevation += de; + + /* debug code */ + //% Sys_Printf( "%d: Angle: %3.4f Elevation: %3.3f\n", sun->numSamples, (angle / Q_PI * 180.0f), (elevation / Q_PI * 180.0f) ); + + /* create new vector */ + direction[ 0 ] = cos( angle ) * cos( elevation ); + direction[ 1 ] = sin( angle ) * cos( elevation ); + direction[ 2 ] = sin( elevation ); + } + + /* create a light */ + numSunLights++; + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + light->next = lights; + lights = light; + + /* initialize the light */ + light->flags = LIGHT_SUN_DEFAULT; + light->type = EMIT_SUN; + light->fade = 1.0f; + light->falloffTolerance = falloffTolerance; + light->filterRadius = sun->filterRadius / sun->numSamples; + light->style = noStyles ? LS_NORMAL : sun->style; + + /* set the light's position out to infinity */ + VectorMA( vec3_origin, ( MAX_WORLD_COORD * 8.0f ), direction, light->origin ); /* MAX_WORLD_COORD * 2.0f */ + + /* set the facing to be the inverse of the sun direction */ + VectorScale( direction, -1.0, light->normal ); + light->dist = DotProduct( light->origin, light->normal ); + + /* set color and photons */ + VectorCopy( sun->color, light->color ); + light->photons = photons * skyScale; + } + + /* another sun? */ + if ( sun->next != NULL ) { + CreateSunLight( sun->next ); + } +} + + + +/* + CreateSkyLights() - ydnar + simulates sky light with multiple suns + */ + +static void CreateSkyLights( vec3_t color, float value, int iterations, float filterRadius, int style ){ + int i, j, numSuns; + int angleSteps, elevationSteps; + float angle, elevation; + float angleStep, elevationStep; + sun_t sun; + + + /* dummy check */ + if ( value <= 0.0f || iterations < 2 ) { + return; + } + + /* basic sun setup */ + VectorCopy( color, sun.color ); + sun.deviance = 0.0f; + sun.filterRadius = filterRadius; + sun.numSamples = 1; + sun.style = noStyles ? LS_NORMAL : style; + sun.next = NULL; + + /* setup */ + elevationSteps = iterations - 1; + angleSteps = elevationSteps * 4; + angle = 0.0f; + elevationStep = DEG2RAD( 90.0f / iterations ); /* skip elevation 0 */ + angleStep = DEG2RAD( 360.0f / angleSteps ); + + /* calc individual sun brightness */ + numSuns = angleSteps * elevationSteps + 1; + sun.photons = value / numSuns; + + /* iterate elevation */ + elevation = elevationStep * 0.5f; + angle = 0.0f; + for ( i = 0, elevation = elevationStep * 0.5f; i < elevationSteps; i++ ) + { + /* iterate angle */ + for ( j = 0; j < angleSteps; j++ ) + { + /* create sun */ + sun.direction[ 0 ] = cos( angle ) * cos( elevation ); + sun.direction[ 1 ] = sin( angle ) * cos( elevation ); + sun.direction[ 2 ] = sin( elevation ); + CreateSunLight( &sun ); + + /* move */ + angle += angleStep; + } + + /* move */ + elevation += elevationStep; + angle += angleStep / elevationSteps; + } + + /* create vertical sun */ + VectorSet( sun.direction, 0.0f, 0.0f, 1.0f ); + CreateSunLight( &sun ); + + /* short circuit */ + return; +} + + + +/* + CreateEntityLights() + creates lights from light entities + */ + +void CreateEntityLights( void ){ + int i, j; + light_t *light, *light2; + entity_t *e, *e2; + const char *name; + const char *target; + vec3_t dest; + const char *_color; + float intensity, scale, deviance, filterRadius; + int spawnflags, flags, numSamples; + qboolean junior; + + + /* go throught entity list and find lights */ + for ( i = 0; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + name = ValueForKey( e, "classname" ); + + if (Q_strncasecmp(name, "light_environment", 17) == 0) { + sun_t *sun; + float a, b; + sun = safe_malloc( sizeof( *sun ) ); + memset( sun, 0, sizeof( *sun ) ); + + /* set style */ + sun->style = IntForKey( e, "style" ); + + _color = ValueForKey( e, "color" ); + if ( _color && _color[ 0 ] ) { + sscanf( _color, "%f %f %f", &sun->color[ 0 ], &sun->color[ 1 ], &sun->color[ 2 ] ); + if ( colorsRGB ) { + sun->color[0] = Image_LinearFloatFromsRGBFloat( sun->color[0] ); + sun->color[1] = Image_LinearFloatFromsRGBFloat( sun->color[1] ); + sun->color[2] = Image_LinearFloatFromsRGBFloat( sun->color[2] ); + } + } else { + /* alternative: read color in RGB8 values -eukara */ + _color = ValueForKey( e, "color255" ); + if ( _color && _color[ 0 ] ) { + sscanf( _color, "%f %f %f", &sun->color[ 0 ], &sun->color[ 1 ], &sun->color[ 2 ] ); + sun->color[0] /= 255; + sun->color[1] /= 255; + sun->color[2] /= 255; + } else { + /* default to white color values */ + sun->color[ 0 ] = + sun->color[ 1 ] = + sun->color[ 2 ] = 1.0f; + } + } + + /* global ambient */ + _color = ValueForKey( e, "ambientcolor" ); + if ( _color && _color[ 0 ] ) { + sscanf( _color, "%f %f %f", &ambientColor[ 0 ], &ambientColor[ 1 ], &ambientColor[ 2 ] ); + if ( colorsRGB ) { + ambientColor[0] = Image_LinearFloatFromsRGBFloat( sun->color[0] ); + ambientColor[1] = Image_LinearFloatFromsRGBFloat( sun->color[1] ); + ambientColor[2] = Image_LinearFloatFromsRGBFloat( sun->color[2] ); + } + } else { + /* alternative: read color in RGB8 values -eukara */ + _color = ValueForKey( e, "ambientcolor255" ); + if ( _color && _color[ 0 ] ) { + sscanf( _color, "%f %f %f", &ambientColor[ 0 ], &ambientColor[ 1 ], &ambientColor[ 2 ] ); + ambientColor[0] /= 255; + ambientColor[1] /= 255; + ambientColor[2] /= 255; + } else { + /* default to white color values */ + ambientColor[ 0 ] = + ambientColor[ 1 ] = + ambientColor[ 2 ] = 1.0f; + } + } + + /* normalize it */ + ColorNormalize( sun->color, sun->color ); + + /* scale color by brightness */ + sun->photons = FloatForKey( e, "intensity" ) * 0.5f; + + /* get sun angle/elevation */ + a = FloatForKey( e, "sunangle" ); + a = a / 180.0f * Q_PI; + + b = FloatForKey( e, "pitch" ) * -1; + b = b / 180.0f * Q_PI; + + sun->direction[ 0 ] = cos( a ) * cos( b ); + sun->direction[ 1 ] = sin( a ) * cos( b ); + sun->direction[ 2 ] = sin( b ); + + /* get filter radius from shader */ + sun->filterRadius = FloatForKey( e, "filterradius" ); + sun->deviance = FloatForKey( e, "sunspreadangle" ); + sun->deviance = sun->deviance / 180.0f * Q_PI; + sun->numSamples = IntForKey( e, "samples" ); + + /* instead of radiosity */ + floodlightRGB[0] = ambientColor[0]; + floodlightRGB[1] = ambientColor[1]; + floodlightRGB[2] = ambientColor[2]; + floodlightDistance = 1024; + floodlightIntensity = sun->photons * 0.5f; + floodlightDirectionScale = 1; + floodlighty = qtrue; + + /* store sun */ + CreateSunLight(sun); + continue; + } + + /* ydnar: check for lightJunior */ + if ( Q_strncasecmp( name, "lightJunior", 11 ) == 0 ) { + junior = qtrue; + } + else if ( Q_strncasecmp( name, "light", 5 ) == 0 ) { + junior = qfalse; + } + else{ + continue; + } + + /* lights with target names (and therefore styles) are only parsed from BSP */ + target = ValueForKey( e, "targetname" ); + if ( target[ 0 ] != '\0' && i >= numBSPEntities ) { + continue; + } + + /* create a light */ + numPointLights++; + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + light->next = lights; + lights = light; + + /* handle spawnflags */ + spawnflags = IntForKey( e, "spawnflags" ); + + /* ydnar: quake 3+ light behavior */ + if ( wolfLight == qfalse ) { + /* set default flags */ + flags = LIGHT_Q3A_DEFAULT; + + /* linear attenuation? */ + /*if ( spawnflags & 1 ) { + flags |= LIGHT_ATTEN_LINEAR; + flags &= ~LIGHT_ATTEN_ANGLE; + }*/ + + /* no angle attenuate? */ + /*if ( spawnflags & 2 ) { + flags &= ~LIGHT_ATTEN_ANGLE; + }*/ + } + + /* ydnar: wolf light behavior */ + else + { + /* set default flags */ + flags = LIGHT_WOLF_DEFAULT; + + /* inverse distance squared attenuation? */ + /*if ( spawnflags & 1 ) { + flags &= ~LIGHT_ATTEN_LINEAR; + flags |= LIGHT_ATTEN_ANGLE; + }*/ + + /* angle attenuate? */ + /*if ( spawnflags & 2 ) { + flags |= LIGHT_ATTEN_ANGLE; + }*/ + } + + /* other flags (borrowed from wolf) */ + + /* wolf dark light? */ + if ( ( spawnflags & 4 ) || ( spawnflags & 8 ) ) { + flags |= LIGHT_DARK; + } + + /* nogrid? */ + if ( spawnflags & 16 ) { + flags &= ~LIGHT_GRID; + } + + /* junior? */ + if ( junior ) { + flags |= LIGHT_GRID; + flags &= ~LIGHT_SURFACES; + } + + /* vortex: unnormalized? */ + /*if ( spawnflags & 32 ) { + flags |= LIGHT_UNNORMALIZED; + }*/ + + /* vortex: distance atten? */ + if ( spawnflags & 64 ) { + flags |= LIGHT_ATTEN_DISTANCE; + } + + /* store the flags */ + light->flags = flags; + + /* ydnar: set fade key (from wolf) */ + light->fade = 1.0f; + if ( light->flags & LIGHT_ATTEN_LINEAR ) { + light->fade = FloatForKey( e, "fade" ); + if ( light->fade == 0.0f ) { + light->fade = 1.0f; + } + } + + /* ydnar: set angle scaling (from vlight) */ + light->angleScale = FloatForKey( e, "_anglescale" ); + if ( light->angleScale != 0.0f ) { + light->flags |= LIGHT_ATTEN_ANGLE; + } + + /* set origin */ + GetVectorForKey( e, "origin", light->origin ); + light->style = IntForKey( e, "_style" ); + if ( light->style == LS_NORMAL ) { + light->style = IntForKey( e, "style" ); + } + if ( light->style < LS_NORMAL || light->style >= LS_NONE ) { + Error( "Invalid lightstyle (%d) on entity %d", light->style, i ); + } + + if ( light->style != LS_NORMAL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Styled light found targeting %s\n **", target ); + } + + /* set light intensity */ + intensity = FloatForKey( e, "_light" ); + if ( intensity == 0.0f ) { + intensity = FloatForKey( e, "light" ); + } + if ( intensity == 0.0f ) { + intensity = 300.0f; + } + + /* ydnar: set light scale (sof2) */ + scale = FloatForKey( e, "scale" ); + if ( scale == 0.0f ) { + scale = 1.0f; + } + intensity *= scale; + + /* ydnar: get deviance and samples */ + deviance = FloatForKey( e, "_deviance" ); + if ( deviance == 0.0f ) { + deviance = FloatForKey( e, "_deviation" ); + } + if ( deviance == 0.0f ) { + deviance = FloatForKey( e, "_jitter" ); + } + numSamples = IntForKey( e, "_samples" ); + if ( deviance < 0.0f || numSamples < 1 ) { + deviance = 0.0f; + numSamples = 1; + } + intensity /= numSamples; + + /* ydnar: get filter radius */ + filterRadius = FloatForKey( e, "_filterradius" ); + if ( filterRadius == 0.0f ) { + filterRadius = FloatForKey( e, "_filteradius" ); + } + if ( filterRadius == 0.0f ) { + filterRadius = FloatForKey( e, "_filter" ); + } + if ( filterRadius < 0.0f ) { + filterRadius = 0.0f; + } + light->filterRadius = filterRadius; + + /* set light color */ + _color = ValueForKey( e, "_color" ); + if ( _color && _color[ 0 ] ) { + sscanf( _color, "%f %f %f", &light->color[ 0 ], &light->color[ 1 ], &light->color[ 2 ] ); + if ( colorsRGB ) { + light->color[0] = Image_LinearFloatFromsRGBFloat( light->color[0] ); + light->color[1] = Image_LinearFloatFromsRGBFloat( light->color[1] ); + light->color[2] = Image_LinearFloatFromsRGBFloat( light->color[2] ); + } + /*if ( !( light->flags & LIGHT_UNNORMALIZED ) ) { + ColorNormalize( light->color, light->color ); + }*/ + } else { + /* alternative: read color in RGB8 values -eukara */ + _color = ValueForKey( e, "_color255" ); + if ( _color && _color[ 0 ] ) { + sscanf( _color, "%f %f %f", &light->color[ 0 ], &light->color[ 1 ], &light->color[ 2 ] ); + light->color[0] /= 255; + light->color[1] /= 255; + light->color[2] /= 255; + /*if ( !( light->flags & LIGHT_UNNORMALIZED ) ) { + ColorNormalize( light->color, light->color ); + }*/ + } else { + /* default to white color values */ + light->color[ 0 ] = + light->color[ 1 ] = + light->color[ 2 ] = 1.0f; + } + } + + light->extraDist = FloatForKey( e, "_extradist" ); + if ( light->extraDist == 0.0f ) { + light->extraDist = extraDist; + } + + light->photons = intensity; + + light->type = EMIT_POINT; + + /* set falloff threshold */ + light->falloffTolerance = falloffTolerance / numSamples; + + /* lights with a target will be spotlights */ + target = ValueForKey( e, "target" ); + if ( target[ 0 ] ) { + float radius; + float dist; + sun_t sun; + const char *_sun; + + + /* get target */ + e2 = FindTargetEntity( target ); + if ( e2 == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: light at (%i %i %i) has missing target\n", + (int) light->origin[ 0 ], (int) light->origin[ 1 ], (int) light->origin[ 2 ] ); + light->photons *= pointScale; + } + else + { + /* not a point light */ + numPointLights--; + numSpotLights++; + + /* make a spotlight */ + GetVectorForKey( e2, "origin", dest ); + VectorSubtract( dest, light->origin, light->normal ); + dist = VectorNormalize( light->normal, light->normal ); + radius = FloatForKey( e, "radius" ); + if ( !radius ) { + radius = 64; + } + if ( !dist ) { + dist = 64; + } + light->radiusByDist = ( radius + 16 ) / dist; + light->type = EMIT_SPOT; + + /* ydnar: wolf mods: spotlights always use nonlinear + angle attenuation */ + light->flags &= ~LIGHT_ATTEN_LINEAR; + light->flags |= LIGHT_ATTEN_ANGLE; + light->fade = 1.0f; + + /* ydnar: is this a sun? */ + _sun = ValueForKey( e, "_sun" ); + if ( _sun[ 0 ] == '1' ) { + /* not a spot light */ + numSpotLights--; + + /* unlink this light */ + lights = light->next; + + /* make a sun */ + VectorScale( light->normal, -1.0f, sun.direction ); + VectorCopy( light->color, sun.color ); + sun.photons = intensity; + sun.deviance = deviance / 180.0f * Q_PI; + sun.numSamples = numSamples; + sun.style = noStyles ? LS_NORMAL : light->style; + sun.next = NULL; + + /* make a sun light */ + CreateSunLight( &sun ); + + /* free original light */ + free( light ); + light = NULL; + + /* skip the rest of this love story */ + continue; + } + else + { + light->photons *= spotScale; + } + } + } + else{ + light->photons *= pointScale; + } + + /* jitter the light */ + for ( j = 1; j < numSamples; j++ ) + { + /* create a light */ + light2 = safe_malloc( sizeof( *light ) ); + memcpy( light2, light, sizeof( *light ) ); + light2->next = lights; + lights = light2; + + /* add to counts */ + if ( light->type == EMIT_SPOT ) { + numSpotLights++; + } + else{ + numPointLights++; + } + + /* jitter it */ + light2->origin[ 0 ] = light->origin[ 0 ] + ( Random() * 2.0f - 1.0f ) * deviance; + light2->origin[ 1 ] = light->origin[ 1 ] + ( Random() * 2.0f - 1.0f ) * deviance; + light2->origin[ 2 ] = light->origin[ 2 ] + ( Random() * 2.0f - 1.0f ) * deviance; + } + } +} + + + +/* + CreateSurfaceLights() - ydnar + this hijacks the radiosity code to generate surface lights for first pass + */ + +#define APPROX_BOUNCE 1.0f + +void CreateSurfaceLights( void ){ + int i; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + shaderInfo_t *si; + light_t *light; + float subdivide; + vec3_t origin; + clipWork_t cw; + const char *nss; + + + /* get sun shader supressor */ + nss = ValueForKey( &entities[ 0 ], "_noshadersun" ); + + /* walk the list of surfaces */ + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get surface and other bits */ + ds = &bspDrawSurfaces[ i ]; + info = &surfaceInfos[ i ]; + si = info->si; + + /* sunlight? */ + if ( si->sun != NULL && nss[ 0 ] != '1' ) { + Sys_FPrintf( SYS_VRB, "Sun: %s\n", si->shader ); + CreateSunLight( si->sun ); + si->sun = NULL; /* FIXME: leak! */ + } + + /* sky light? */ + if ( si->skyLightValue > 0.0f ) { + Sys_FPrintf( SYS_VRB, "Sky: %s\n", si->shader ); + CreateSkyLights( si->color, si->skyLightValue, si->skyLightIterations, si->lightFilterRadius, si->lightStyle ); + si->skyLightValue = 0.0f; /* FIXME: hack! */ + } + + /* try to early out */ + if ( si->value <= 0 ) { + continue; + } + + /* autosprite shaders become point lights */ + if ( si->autosprite ) { + /* create an average xyz */ + VectorAdd( info->mins, info->maxs, origin ); + VectorScale( origin, 0.5f, origin ); + + /* create a light */ + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + light->next = lights; + lights = light; + + /* set it up */ + light->flags = LIGHT_Q3A_DEFAULT; + light->type = EMIT_POINT; + light->photons = si->value * pointScale; + light->fade = 1.0f; + light->si = si; + VectorCopy( origin, light->origin ); + VectorCopy( si->color, light->color ); + light->falloffTolerance = falloffTolerance; + light->style = si->lightStyle; + + /* add to point light count and continue */ + numPointLights++; + continue; + } + + /* get subdivision amount */ + if ( si->lightSubdivide > 0 ) { + subdivide = si->lightSubdivide; + } + else{ + subdivide = defaultLightSubdivide; + } + + /* switch on type */ + switch ( ds->surfaceType ) + { + case MST_PLANAR: + case MST_TRIANGLE_SOUP: + RadLightForTriangles( i, 0, info->lm, si, APPROX_BOUNCE, subdivide, &cw ); + break; + + case MST_PATCH: + case MST_PATCHFIXED: + RadLightForPatch( i, 0, info->lm, si, APPROX_BOUNCE, subdivide, &cw, (ds->surfaceType==MST_PATCHFIXED)?qtrue:qfalse ); + break; + + default: + break; + } + } +} + + + +/* + SetEntityOrigins() + find the offset values for inline models + */ + +void SetEntityOrigins( void ){ + int i, j, k, f; + entity_t *e; + vec3_t origin; + const char *key; + int modelnum; + bspModel_t *dm; + bspDrawSurface_t *ds; + + + /* ydnar: copy drawverts into private storage for nefarious purposes */ + yDrawVerts = safe_malloc( numBSPDrawVerts * sizeof( bspDrawVert_t ) ); + memcpy( yDrawVerts, bspDrawVerts, numBSPDrawVerts * sizeof( bspDrawVert_t ) ); + + /* set the entity origins */ + for ( i = 0; i < numEntities; i++ ) + { + /* get entity and model */ + e = &entities[ i ]; + key = ValueForKey( e, "model" ); + if ( key[ 0 ] != '*' ) { + continue; + } + modelnum = atoi( key + 1 ); + dm = &bspModels[ modelnum ]; + + /* get entity origin */ + key = ValueForKey( e, "origin" ); + if ( key[ 0 ] == '\0' ) { + continue; + } + GetVectorForKey( e, "origin", origin ); + + /* set origin for all surfaces for this model */ + for ( j = 0; j < dm->numBSPSurfaces; j++ ) + { + /* get drawsurf */ + ds = &bspDrawSurfaces[ dm->firstBSPSurface + j ]; + + /* set its verts */ + for ( k = 0; k < ds->numVerts; k++ ) + { + f = ds->firstVert + k; + VectorAdd( origin, bspDrawVerts[ f ].xyz, yDrawVerts[ f ].xyz ); + } + } + } +} + + + +/* + PointToPolygonFormFactor() + calculates the area over a point/normal hemisphere a winding covers + ydnar: fixme: there has to be a faster way to calculate this + without the expensive per-vert sqrts and transcendental functions + ydnar 2002-09-30: added -faster switch because only 19% deviance > 10% + between this and the approximation + */ + +#define ONE_OVER_2PI 0.159154942f //% (1.0f / (2.0f * 3.141592657f)) + +float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const winding_t *w ){ + vec3_t triVector, triNormal; + int i, j; + vec3_t dirs[ MAX_POINTS_ON_WINDING ]; + float total; + float dot, angle, facing; + + + /* this is expensive */ + for ( i = 0; i < w->numpoints; i++ ) + { + VectorSubtract( w->p[ i ], point, dirs[ i ] ); + VectorNormalize( dirs[ i ], dirs[ i ] ); + } + + /* duplicate first vertex to avoid mod operation */ + VectorCopy( dirs[ 0 ], dirs[ i ] ); + + /* calculcate relative area */ + total = 0.0f; + for ( i = 0; i < w->numpoints; i++ ) + { + /* get a triangle */ + j = i + 1; + dot = DotProduct( dirs[ i ], dirs[ j ] ); + + /* roundoff can cause slight creep, which gives an IND from acos */ + if ( dot > 1.0f ) { + dot = 1.0f; + } + else if ( dot < -1.0f ) { + dot = -1.0f; + } + + /* get the angle */ + angle = acos( dot ); + + CrossProduct( dirs[ i ], dirs[ j ], triVector ); + if ( VectorNormalize( triVector, triNormal ) < 0.0001f ) { + continue; + } + + facing = DotProduct( normal, triNormal ); + total += facing * angle; + + /* ydnar: this was throwing too many errors with radiosity + crappy maps. ignoring it. */ + if ( total > 6.3f || total < -6.3f ) { + return 0.0f; + } + } + + /* now in the range of 0 to 1 over the entire incoming hemisphere */ + //% total /= (2.0f * 3.141592657f); + total *= ONE_OVER_2PI; + return total; +} + + + +/* + LightContributionTosample() + determines the amount of light reaching a sample (luxel or vertex) from a given light + */ + +int LightContributionToSample( trace_t *trace ){ + light_t *light; + float angle; + float add; + float dist; + float addDeluxe = 0.0f, addDeluxeBounceScale = 0.25f; + qboolean angledDeluxe = qtrue; + float colorBrightness; + qboolean doAddDeluxe = qtrue; + + /* get light */ + light = trace->light; + + /* clear color */ + trace->forceSubsampling = 0.0f; /* to make sure */ + VectorClear( trace->color ); + VectorClear( trace->colorNoShadow ); + VectorClear( trace->directionContribution ); + + colorBrightness = RGBTOGRAY( light->color ) * ( 1.0f / 255.0f ); + + /* ydnar: early out */ + if ( !( light->flags & LIGHT_SURFACES ) || light->envelope <= 0.0f ) { + return 0; + } + + /* do some culling checks */ + if ( light->type != EMIT_SUN ) { + /* MrE: if the light is behind the surface */ + if ( trace->twoSided == qfalse ) { + if ( DotProduct( light->origin, trace->normal ) - DotProduct( trace->origin, trace->normal ) < 0.0f ) { + return 0; + } + } + + /* ydnar: test pvs */ + if ( !ClusterVisible( trace->cluster, light->cluster ) ) { + return 0; + } + } + + /* exact point to polygon form factor */ + if ( light->type == EMIT_AREA ) { + float factor; + float d; + vec3_t pushedOrigin; + + /* project sample point into light plane */ + d = DotProduct( trace->origin, light->normal ) - light->dist; + if ( d < 3.0f ) { + /* sample point behind plane? */ + if ( !( light->flags & LIGHT_TWOSIDED ) && d < -1.0f ) { + return 0; + } + + /* sample plane coincident? */ + if ( d > -3.0f && DotProduct( trace->normal, light->normal ) > 0.9f ) { + return 0; + } + } + + /* nudge the point so that it is clearly forward of the light */ + /* so that surfaces meeting a light emitter don't get black edges */ + if ( d > -8.0f && d < 8.0f ) { + VectorMA( trace->origin, ( 8.0f - d ), light->normal, pushedOrigin ); + } + else{ + VectorCopy( trace->origin, pushedOrigin ); + } + + /* get direction and distance */ + VectorCopy( light->origin, trace->end ); + dist = SetupTrace( trace ); + if ( dist >= light->envelope ) { + return 0; + } + + /* ptpff approximation */ + if ( faster ) { + /* angle attenuation */ + angle = DotProduct( trace->normal, trace->direction ); + + /* twosided lighting */ + if ( trace->twoSided && angle < 0 ) { + angle = -angle; + + /* no deluxemap contribution from "other side" light */ + doAddDeluxe = qfalse; + } + + /* attenuate */ + angle *= -DotProduct( light->normal, trace->direction ); + if ( angle == 0.0f ) { + return 0; + } + else if ( angle < 0.0f && + ( trace->twoSided || ( light->flags & LIGHT_TWOSIDED ) ) ) { + angle = -angle; + + /* no deluxemap contribution from "other side" light */ + doAddDeluxe = qfalse; + } + + /* clamp the distance to prevent super hot spots */ + dist = sqrt( dist * dist + light->extraDist * light->extraDist ); + if ( dist < 16.0f ) { + dist = 16.0f; + } + + add = light->photons / ( dist * dist ) * angle; + + if ( deluxemap ) { + if ( angledDeluxe ) { + addDeluxe = light->photons / ( dist * dist ) * angle; + } + else{ + addDeluxe = light->photons / ( dist * dist ); + } + } + } + else + { + /* calculate the contribution */ + factor = PointToPolygonFormFactor( pushedOrigin, trace->normal, light->w ); + if ( factor == 0.0f ) { + return 0; + } + else if ( factor < 0.0f ) { + /* twosided lighting */ + if ( trace->twoSided || ( light->flags & LIGHT_TWOSIDED ) ) { + factor = -factor; + + /* push light origin to other side of the plane */ + VectorMA( light->origin, -2.0f, light->normal, trace->end ); + dist = SetupTrace( trace ); + if ( dist >= light->envelope ) { + return 0; + } + + /* no deluxemap contribution from "other side" light */ + doAddDeluxe = qfalse; + } + else{ + return 0; + } + } + + /* also don't deluxe if the direction is on the wrong side */ + if ( DotProduct( trace->normal, trace->direction ) < 0 ) { + /* no deluxemap contribution from "other side" light */ + doAddDeluxe = qfalse; + } + + /* ydnar: moved to here */ + add = factor * light->add; + + if ( deluxemap ) { + addDeluxe = add; + } + } + } + + /* point/spot lights */ + else if ( light->type == EMIT_POINT || light->type == EMIT_SPOT ) { + /* get direction and distance */ + VectorCopy( light->origin, trace->end ); + dist = SetupTrace( trace ); + if ( dist >= light->envelope ) { + return 0; + } + + /* clamp the distance to prevent super hot spots */ + dist = sqrt( dist * dist + light->extraDist * light->extraDist ); + if ( dist < 16.0f ) { + dist = 16.0f; + } + + /* angle attenuation */ + if ( light->flags & LIGHT_ATTEN_ANGLE ) { + /* standard Lambert attenuation */ + float dot = DotProduct( trace->normal, trace->direction ); + + /* twosided lighting */ + if ( trace->twoSided && dot < 0 ) { + dot = -dot; + + /* no deluxemap contribution from "other side" light */ + doAddDeluxe = qfalse; + } + + /* jal: optional half Lambert attenuation (http://developer.valvesoftware.com/wiki/Half_Lambert) */ + if ( lightAngleHL ) { + if ( dot > 0.001f ) { // skip coplanar + if ( dot > 1.0f ) { + dot = 1.0f; + } + dot = ( dot * 0.5f ) + 0.5f; + dot *= dot; + } + else{ + dot = 0; + } + } + + angle = dot; + } + else{ + angle = 1.0f; + } + + if ( light->angleScale != 0.0f ) { + angle /= light->angleScale; + if ( angle > 1.0f ) { + angle = 1.0f; + } + } + + /* attenuate */ + if ( light->flags & LIGHT_ATTEN_LINEAR ) { + add = angle * light->photons * linearScale - ( dist * light->fade ); + if ( add < 0.0f ) { + add = 0.0f; + } + + if ( deluxemap ) { + if ( angledDeluxe ) { + addDeluxe = angle * light->photons * linearScale - ( dist * light->fade ); + } + else{ + addDeluxe = light->photons * linearScale - ( dist * light->fade ); + } + + if ( addDeluxe < 0.0f ) { + addDeluxe = 0.0f; + } + } + } + else + { + add = ( light->photons / ( dist * dist ) ) * angle; + if ( add < 0.0f ) { + add = 0.0f; + } + + if ( deluxemap ) { + if ( angledDeluxe ) { + addDeluxe = ( light->photons / ( dist * dist ) ) * angle; + } + else{ + addDeluxe = ( light->photons / ( dist * dist ) ); + } + } + + if ( addDeluxe < 0.0f ) { + addDeluxe = 0.0f; + } + } + + /* handle spotlights */ + if ( light->type == EMIT_SPOT ) { + float distByNormal, radiusAtDist, sampleRadius; + vec3_t pointAtDist, distToSample; + + /* do cone calculation */ + distByNormal = -DotProduct( trace->displacement, light->normal ); + if ( distByNormal < 0.0f ) { + return 0; + } + VectorMA( light->origin, distByNormal, light->normal, pointAtDist ); + radiusAtDist = light->radiusByDist * distByNormal; + VectorSubtract( trace->origin, pointAtDist, distToSample ); + sampleRadius = VectorLength( distToSample ); + + /* outside the cone */ + if ( sampleRadius >= radiusAtDist ) { + return 0; + } + + /* attenuate */ + if ( sampleRadius > ( radiusAtDist - 32.0f ) ) { + add *= ( ( radiusAtDist - sampleRadius ) / 32.0f ); + if ( add < 0.0f ) { + add = 0.0f; + } + + addDeluxe *= ( ( radiusAtDist - sampleRadius ) / 32.0f ); + + if ( addDeluxe < 0.0f ) { + addDeluxe = 0.0f; + } + } + } + } + + /* ydnar: sunlight */ + else if ( light->type == EMIT_SUN ) { + /* get origin and direction */ + VectorAdd( trace->origin, light->origin, trace->end ); + dist = SetupTrace( trace ); + + /* angle attenuation */ + if ( light->flags & LIGHT_ATTEN_ANGLE ) { + /* standard Lambert attenuation */ + float dot = DotProduct( trace->normal, trace->direction ); + + /* twosided lighting */ + if ( trace->twoSided && dot < 0 ) { + dot = -dot; + + /* no deluxemap contribution from "other side" light */ + doAddDeluxe = qfalse; + } + + /* jal: optional half Lambert attenuation (http://developer.valvesoftware.com/wiki/Half_Lambert) */ + if ( lightAngleHL ) { + if ( dot > 0.001f ) { // skip coplanar + if ( dot > 1.0f ) { + dot = 1.0f; + } + dot = ( dot * 0.5f ) + 0.5f; + dot *= dot; + } + else{ + dot = 0; + } + } + + angle = dot; + } + else{ + angle = 1.0f; + } + + /* attenuate */ + add = light->photons * angle; + + if ( deluxemap ) { + if ( angledDeluxe ) { + addDeluxe = light->photons * angle; + } + else{ + addDeluxe = light->photons; + } + + if ( addDeluxe < 0.0f ) { + addDeluxe = 0.0f; + } + } + + if ( add <= 0.0f ) { + return 0; + } + + /* VorteX: set noShadow color */ + VectorScale( light->color, add, trace->colorNoShadow ); + + addDeluxe *= colorBrightness; + + if ( bouncing ) { + addDeluxe *= addDeluxeBounceScale; + if ( addDeluxe < 0.00390625f ) { + addDeluxe = 0.00390625f; + } + } + + VectorScale( trace->direction, addDeluxe, trace->directionContribution ); + + /* setup trace */ + trace->testAll = qtrue; + VectorScale( light->color, add, trace->color ); + + /* trace to point */ + if ( trace->testOcclusion && !trace->forceSunlight ) { + /* trace */ + TraceLine( trace ); + trace->forceSubsampling *= add; + if ( !( trace->compileFlags & C_SKY ) || trace->opaque ) { + VectorClear( trace->color ); + VectorClear( trace->directionContribution ); + + return -1; + } + } + + /* return to sender */ + return 1; + } + else { + Error( "Light of undefined type!" ); + } + + /* VorteX: set noShadow color */ + VectorScale( light->color, add, trace->colorNoShadow ); + + /* ydnar: changed to a variable number */ + if ( add <= 0.0f || ( add <= light->falloffTolerance && ( light->flags & LIGHT_FAST_ACTUAL ) ) ) { + return 0; + } + + addDeluxe *= colorBrightness; + + /* hack land: scale down the radiosity contribution to light directionality. + Deluxemaps fusion many light directions into one. In a rtl process all lights + would contribute individually to the bump map, so several light sources together + would make it more directional (example: a yellow and red lights received from + opposing sides would light one side in red and the other in blue, adding + the effect of 2 directions applied. In the deluxemapping case, this 2 lights would + neutralize each other making it look like having no direction. + Same thing happens with radiosity. In deluxemapping case the radiosity contribution + is modifying the direction applied from directional lights, making it go closer and closer + to the surface normal the bigger is the amount of radiosity received. + So, for preserving the directional lights contributions, we scale down the radiosity + contribution. It's a hack, but there's a reason behind it */ + if ( bouncing ) { + addDeluxe *= addDeluxeBounceScale; + /* better NOT increase it beyond the original value + if( addDeluxe < 0.00390625f ) + addDeluxe = 0.00390625f; + */ + } + + if ( doAddDeluxe ) { + VectorScale( trace->direction, addDeluxe, trace->directionContribution ); + } + + /* setup trace */ + trace->testAll = qfalse; + VectorScale( light->color, add, trace->color ); + + /* raytrace */ + TraceLine( trace ); + trace->forceSubsampling *= add; + if ( trace->passSolid || trace->opaque ) { + VectorClear( trace->color ); + VectorClear( trace->directionContribution ); + + return -1; + } + + /* return to sender */ + return 1; +} + + + +/* + LightingAtSample() + determines the amount of light reaching a sample (luxel or vertex) + */ + +void LightingAtSample( trace_t *trace, byte styles[ MAX_LIGHTMAPS ], vec3_t colors[ MAX_LIGHTMAPS ] ){ + int i, lightmapNum; + + + /* clear colors */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + VectorClear( colors[ lightmapNum ] ); + + /* ydnar: normalmap */ + if ( normalmap ) { + colors[ 0 ][ 0 ] = ( trace->normal[ 0 ] + 1.0f ) * 127.5f; + colors[ 0 ][ 1 ] = ( trace->normal[ 1 ] + 1.0f ) * 127.5f; + colors[ 0 ][ 2 ] = ( trace->normal[ 2 ] + 1.0f ) * 127.5f; + return; + } + + /* ydnar: don't bounce ambient all the time */ + if ( !bouncing ) { + VectorCopy( ambientColor, colors[ 0 ] ); + } + + /* ydnar: trace to all the list of lights pre-stored in tw */ + for ( i = 0; i < trace->numLights && trace->lights[ i ] != NULL; i++ ) + { + /* set light */ + trace->light = trace->lights[ i ]; + + /* style check */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + if ( styles[ lightmapNum ] == trace->light->style || + styles[ lightmapNum ] == LS_NONE ) { + break; + } + } + + /* max of MAX_LIGHTMAPS (4) styles allowed to hit a sample */ + if ( lightmapNum >= MAX_LIGHTMAPS ) { + continue; + } + + /* sample light */ + LightContributionToSample( trace ); + if ( trace->color[ 0 ] == 0.0f && trace->color[ 1 ] == 0.0f && trace->color[ 2 ] == 0.0f ) { + continue; + } + + /* handle negative light */ + if ( trace->light->flags & LIGHT_NEGATIVE ) { + VectorScale( trace->color, -1.0f, trace->color ); + } + + /* set style */ + styles[ lightmapNum ] = trace->light->style; + + /* add it */ + VectorAdd( colors[ lightmapNum ], trace->color, colors[ lightmapNum ] ); + + /* cheap mode */ + if ( cheap && + colors[ 0 ][ 0 ] >= 255.0f && + colors[ 0 ][ 1 ] >= 255.0f && + colors[ 0 ][ 2 ] >= 255.0f ) { + break; + } + } +} + + + +/* + LightContributionToPoint() + for a given light, how much light/color reaches a given point in space (with no facing) + note: this is similar to LightContributionToSample() but optimized for omnidirectional sampling + */ + +int LightContributionToPoint( trace_t *trace ){ + light_t *light; + float add, dist; + + + /* get light */ + light = trace->light; + + /* clear color */ + VectorClear( trace->color ); + + /* ydnar: early out */ + if ( !( light->flags & LIGHT_GRID ) || light->envelope <= 0.0f ) { + return qfalse; + } + + /* is this a sun? */ + if ( light->type != EMIT_SUN ) { + /* sun only? */ + if ( sunOnly ) { + return qfalse; + } + + /* test pvs */ + if ( !ClusterVisible( trace->cluster, light->cluster ) ) { + return qfalse; + } + } + + /* ydnar: check origin against light's pvs envelope */ + if ( trace->origin[ 0 ] > light->maxs[ 0 ] || trace->origin[ 0 ] < light->mins[ 0 ] || + trace->origin[ 1 ] > light->maxs[ 1 ] || trace->origin[ 1 ] < light->mins[ 1 ] || + trace->origin[ 2 ] > light->maxs[ 2 ] || trace->origin[ 2 ] < light->mins[ 2 ] ) { + gridBoundsCulled++; + return qfalse; + } + + /* set light origin */ + if ( light->type == EMIT_SUN ) { + VectorAdd( trace->origin, light->origin, trace->end ); + } + else{ + VectorCopy( light->origin, trace->end ); + } + + /* set direction */ + dist = SetupTrace( trace ); + + /* test envelope */ + if ( dist > light->envelope ) { + gridEnvelopeCulled++; + return qfalse; + } + + /* ptpff approximation */ + if ( light->type == EMIT_AREA && faster ) { + /* clamp the distance to prevent super hot spots */ + dist = sqrt( dist * dist + light->extraDist * light->extraDist ); + if ( dist < 16.0f ) { + dist = 16.0f; + } + + /* attenuate */ + add = light->photons / ( dist * dist ); + } + + /* exact point to polygon form factor */ + else if ( light->type == EMIT_AREA ) { + float factor, d; + vec3_t pushedOrigin; + + + /* see if the point is behind the light */ + d = DotProduct( trace->origin, light->normal ) - light->dist; + if ( !( light->flags & LIGHT_TWOSIDED ) && d < -1.0f ) { + return qfalse; + } + + /* nudge the point so that it is clearly forward of the light */ + /* so that surfaces meeting a light emiter don't get black edges */ + if ( d > -8.0f && d < 8.0f ) { + VectorMA( trace->origin, ( 8.0f - d ), light->normal, pushedOrigin ); + } + else{ + VectorCopy( trace->origin, pushedOrigin ); + } + + /* calculate the contribution (ydnar 2002-10-21: [bug 642] bad normal calc) */ + factor = PointToPolygonFormFactor( pushedOrigin, trace->direction, light->w ); + if ( factor == 0.0f ) { + return qfalse; + } + else if ( factor < 0.0f ) { + if ( light->flags & LIGHT_TWOSIDED ) { + factor = -factor; + } + else{ + return qfalse; + } + } + + /* ydnar: moved to here */ + add = factor * light->add; + } + + /* point/spot lights */ + else if ( light->type == EMIT_POINT || light->type == EMIT_SPOT ) { + /* clamp the distance to prevent super hot spots */ + dist = sqrt( dist * dist + light->extraDist * light->extraDist ); + if ( dist < 16.0f ) { + dist = 16.0f; + } + + /* attenuate */ + if ( light->flags & LIGHT_ATTEN_LINEAR ) { + add = light->photons * linearScale - ( dist * light->fade ); + if ( add < 0.0f ) { + add = 0.0f; + } + } + else{ + add = light->photons / ( dist * dist ); + } + + /* handle spotlights */ + if ( light->type == EMIT_SPOT ) { + float distByNormal, radiusAtDist, sampleRadius; + vec3_t pointAtDist, distToSample; + + + /* do cone calculation */ + distByNormal = -DotProduct( trace->displacement, light->normal ); + if ( distByNormal < 0.0f ) { + return qfalse; + } + VectorMA( light->origin, distByNormal, light->normal, pointAtDist ); + radiusAtDist = light->radiusByDist * distByNormal; + VectorSubtract( trace->origin, pointAtDist, distToSample ); + sampleRadius = VectorLength( distToSample ); + + /* outside the cone */ + if ( sampleRadius >= radiusAtDist ) { + return qfalse; + } + + /* attenuate */ + if ( sampleRadius > ( radiusAtDist - 32.0f ) ) { + add *= ( ( radiusAtDist - sampleRadius ) / 32.0f ); + } + } + } + + /* ydnar: sunlight */ + else if ( light->type == EMIT_SUN ) { + /* attenuate */ + add = light->photons; + if ( add <= 0.0f ) { + return qfalse; + } + + /* setup trace */ + trace->testAll = qtrue; + VectorScale( light->color, add, trace->color ); + + /* trace to point */ + if ( trace->testOcclusion && !trace->forceSunlight ) { + /* trace */ + TraceLine( trace ); + if ( !( trace->compileFlags & C_SKY ) || trace->opaque ) { + VectorClear( trace->color ); + return -1; + } + } + + /* return to sender */ + return qtrue; + } + + /* unknown light type */ + else{ + return qfalse; + } + + /* ydnar: changed to a variable number */ + if ( add <= 0.0f || ( add <= light->falloffTolerance && ( light->flags & LIGHT_FAST_ACTUAL ) ) ) { + return qfalse; + } + + /* setup trace */ + trace->testAll = qfalse; + VectorScale( light->color, add, trace->color ); + + /* trace */ + TraceLine( trace ); + if ( trace->passSolid ) { + VectorClear( trace->color ); + return qfalse; + } + + /* we have a valid sample */ + return qtrue; +} + + + +/* + TraceGrid() + grid samples are for quickly determining the lighting + of dynamically placed entities in the world + */ + +#define MAX_CONTRIBUTIONS 32768 + +typedef struct +{ + vec3_t dir; + vec3_t color; + vec3_t ambient; + int style; +} +contribution_t; + +void TraceGrid( int num ){ + int i, j, x, y, z, mod, numCon, numStyles; + float d, step; + vec3_t baseOrigin, cheapColor, color, thisdir; + rawGridPoint_t *gp; + bspGridPoint_t *bgp; + contribution_t contributions[ MAX_CONTRIBUTIONS ]; + trace_t trace; + + /* get grid points */ + gp = &rawGridPoints[ num ]; + bgp = &bspGridPoints[ num ]; + + /* get grid origin */ + mod = num; + z = mod / ( gridBounds[ 0 ] * gridBounds[ 1 ] ); + mod -= z * ( gridBounds[ 0 ] * gridBounds[ 1 ] ); + y = mod / gridBounds[ 0 ]; + mod -= y * gridBounds[ 0 ]; + x = mod; + + trace.origin[ 0 ] = gridMins[ 0 ] + x * gridSize[ 0 ]; + trace.origin[ 1 ] = gridMins[ 1 ] + y * gridSize[ 1 ]; + trace.origin[ 2 ] = gridMins[ 2 ] + z * gridSize[ 2 ]; + + /* set inhibit sphere */ + if ( gridSize[ 0 ] > gridSize[ 1 ] && gridSize[ 0 ] > gridSize[ 2 ] ) { + trace.inhibitRadius = gridSize[ 0 ] * 0.5f; + } + else if ( gridSize[ 1 ] > gridSize[ 0 ] && gridSize[ 1 ] > gridSize[ 2 ] ) { + trace.inhibitRadius = gridSize[ 1 ] * 0.5f; + } + else{ + trace.inhibitRadius = gridSize[ 2 ] * 0.5f; + } + + /* find point cluster */ + trace.cluster = ClusterForPointExt( trace.origin, GRID_EPSILON ); + if ( trace.cluster < 0 ) { + /* try to nudge the origin around to find a valid point */ + VectorCopy( trace.origin, baseOrigin ); + for ( step = 0; ( step += 0.005 ) <= 1.0; ) + { + VectorCopy( baseOrigin, trace.origin ); + trace.origin[ 0 ] += step * ( Random() - 0.5 ) * gridSize[0]; + trace.origin[ 1 ] += step * ( Random() - 0.5 ) * gridSize[1]; + trace.origin[ 2 ] += step * ( Random() - 0.5 ) * gridSize[2]; + + /* ydnar: changed to find cluster num */ + trace.cluster = ClusterForPointExt( trace.origin, VERTEX_EPSILON ); + if ( trace.cluster >= 0 ) { + break; + } + } + + /* can't find a valid point at all */ + if ( step > 1.0 ) { + return; + } + } + + /* setup trace */ + trace.testOcclusion = !noTrace; + trace.forceSunlight = qfalse; + trace.recvShadows = WORLDSPAWN_RECV_SHADOWS; + trace.numSurfaces = 0; + trace.surfaces = NULL; + trace.numLights = 0; + trace.lights = NULL; + + /* clear */ + numCon = 0; + VectorClear( cheapColor ); + + /* trace to all the lights, find the major light direction, and divide the + total light between that along the direction and the remaining in the ambient */ + for ( trace.light = lights; trace.light != NULL; trace.light = trace.light->next ) + { + float addSize; + + + /* sample light */ + if ( !LightContributionToPoint( &trace ) ) { + continue; + } + + /* handle negative light */ + if ( trace.light->flags & LIGHT_NEGATIVE ) { + VectorScale( trace.color, -1.0f, trace.color ); + } + + /* add a contribution */ + VectorCopy( trace.color, contributions[ numCon ].color ); + VectorCopy( trace.direction, contributions[ numCon ].dir ); + VectorClear( contributions[ numCon ].ambient ); + contributions[ numCon ].style = trace.light->style; + numCon++; + + /* push average direction around */ + addSize = VectorLength( trace.color ); + VectorMA( gp->dir, addSize, trace.direction, gp->dir ); + + /* stop after a while */ + if ( numCon >= ( MAX_CONTRIBUTIONS - 1 ) ) { + break; + } + + /* ydnar: cheap mode */ + VectorAdd( cheapColor, trace.color, cheapColor ); + if ( cheapgrid && cheapColor[ 0 ] >= 255.0f && cheapColor[ 1 ] >= 255.0f && cheapColor[ 2 ] >= 255.0f ) { + break; + } + } + + /////// Floodlighting for point ////////////////// + //do our floodlight ambient occlusion loop, and add a single contribution based on the brightest dir + if ( floodlighty ) { + int k; + float addSize, f; + vec3_t dir = { 0, 0, 1 }; + float ambientFrac = 0.25f; + + trace.testOcclusion = qtrue; + trace.forceSunlight = qfalse; + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + trace.testAll = qtrue; + + for ( k = 0; k < 2; k++ ) + { + if ( k == 0 ) { // upper hemisphere + trace.normal[0] = 0; + trace.normal[1] = 0; + trace.normal[2] = 1; + } + else //lower hemisphere + { + trace.normal[0] = 0; + trace.normal[1] = 0; + trace.normal[2] = -1; + } + + f = FloodLightForSample( &trace, floodlightDistance, floodlight_lowquality ); + + /* add a fraction as pure ambient, half as top-down direction */ + contributions[ numCon ].color[0] = floodlightRGB[0] * floodlightIntensity * f * ( 1.0f - ambientFrac ); + contributions[ numCon ].color[1] = floodlightRGB[1] * floodlightIntensity * f * ( 1.0f - ambientFrac ); + contributions[ numCon ].color[2] = floodlightRGB[2] * floodlightIntensity * f * ( 1.0f - ambientFrac ); + + contributions[ numCon ].ambient[0] = floodlightRGB[0] * floodlightIntensity * f * ambientFrac; + contributions[ numCon ].ambient[1] = floodlightRGB[1] * floodlightIntensity * f * ambientFrac; + contributions[ numCon ].ambient[2] = floodlightRGB[2] * floodlightIntensity * f * ambientFrac; + + contributions[ numCon ].dir[0] = dir[0]; + contributions[ numCon ].dir[1] = dir[1]; + contributions[ numCon ].dir[2] = dir[2]; + + contributions[ numCon ].style = 0; + + /* push average direction around */ + addSize = VectorLength( contributions[ numCon ].color ); + VectorMA( gp->dir, addSize, dir, gp->dir ); + + numCon++; + } + } + ///////////////////// + + /* normalize to get primary light direction */ + VectorNormalize( gp->dir, thisdir ); + + /* now that we have identified the primary light direction, + go back and separate all the light into directed and ambient */ + + numStyles = 1; + for ( i = 0; i < numCon; i++ ) + { + /* get relative directed strength */ + d = DotProduct( contributions[ i ].dir, thisdir ); + /* we map 1 to gridDirectionality, and 0 to gridAmbientDirectionality */ + d = gridAmbientDirectionality + d * ( gridDirectionality - gridAmbientDirectionality ); + if ( d < 0.0f ) { + d = 0.0f; + } + + /* find appropriate style */ + for ( j = 0; j < numStyles; j++ ) + { + if ( gp->styles[ j ] == contributions[ i ].style ) { + break; + } + } + + /* style not found? */ + if ( j >= numStyles ) { + /* add a new style */ + if ( numStyles < MAX_LIGHTMAPS ) { + gp->styles[ numStyles ] = contributions[ i ].style; + bgp->styles[ numStyles ] = contributions[ i ].style; + numStyles++; + //% Sys_Printf( "(%d, %d) ", num, contributions[ i ].style ); + } + + /* fallback */ + else{ + j = 0; + } + } + + /* add the directed color */ + VectorMA( gp->directed[ j ], d, contributions[ i ].color, gp->directed[ j ] ); + + /* ambient light will be at 1/4 the value of directed light */ + /* (ydnar: nuke this in favor of more dramatic lighting?) */ + /* (PM: how about actually making it work? d=1 when it got here for single lights/sun :P */ +// d = 0.25f; + /* (Hobbes: always setting it to .25 is hardly any better) */ + d = 0.25f * ( 1.0f - d ); + VectorMA( gp->ambient[ j ], d, contributions[ i ].color, gp->ambient[ j ] ); + + VectorAdd( gp->ambient[ j ], contributions[ i ].ambient, gp->ambient[ j ] ); + +/* + * div0: + * the total light average = ambient value + 0.25 * sum of all directional values + * we can also get the total light average as 0.25 * the sum of all contributions + * + * 0.25 * sum(contribution_i) == ambient + 0.25 * sum(d_i contribution_i) + * + * THIS YIELDS: + * ambient == 0.25 * sum((1 - d_i) contribution_i) + * + * So, 0.25f * (1.0f - d) IS RIGHT. If you want to tune it, tune d BEFORE. + */ + } + + + /* store off sample */ + for ( i = 0; i < MAX_LIGHTMAPS; i++ ) + { +#if 0 + /* do some fudging to keep the ambient from being too low (2003-07-05: 0.25 -> 0.125) */ + if ( !bouncing ) { + VectorMA( gp->ambient[ i ], 0.125f, gp->directed[ i ], gp->ambient[ i ] ); + } +#endif + + /* set minimum light and copy off to bytes */ + VectorCopy( gp->ambient[ i ], color ); + for ( j = 0; j < 3; j++ ) + if ( color[ j ] < minGridLight[ j ] ) { + color[ j ] = minGridLight[ j ]; + } + + /* vortex: apply gridscale and gridambientscale here */ + ColorToBytes( color, bgp->ambient[ i ], gridScale * gridAmbientScale ); + ColorToBytes( gp->directed[ i ], bgp->directed[ i ], gridScale ); + } + + /* debug code */ + #if 0 + //% Sys_FPrintf( SYS_VRB, "%10d %10d %10d ", &gp->ambient[ 0 ][ 0 ], &gp->ambient[ 0 ][ 1 ], &gp->ambient[ 0 ][ 2 ] ); + Sys_FPrintf( SYS_VRB, "%9d Amb: (%03.1f %03.1f %03.1f) Dir: (%03.1f %03.1f %03.1f)\n", + num, + gp->ambient[ 0 ][ 0 ], gp->ambient[ 0 ][ 1 ], gp->ambient[ 0 ][ 2 ], + gp->directed[ 0 ][ 0 ], gp->directed[ 0 ][ 1 ], gp->directed[ 0 ][ 2 ] ); + #endif + + /* store direction */ + NormalToLatLong( thisdir, bgp->latLong ); +} + + + +/* + SetupGrid() + calculates the size of the lightgrid and allocates memory + */ + +void SetupGrid( void ){ + int i, j; + vec3_t maxs, oldGridSize; + const char *value; + char temp[ 64 ]; + + + /* don't do this if not grid lighting */ + if ( noGridLighting ) { + return; + } + + /* ydnar: set grid size */ + value = ValueForKey( &entities[ 0 ], "gridsize" ); + if ( value[ 0 ] != '\0' ) { + sscanf( value, "%f %f %f", &gridSize[ 0 ], &gridSize[ 1 ], &gridSize[ 2 ] ); + } + + /* quantize it */ + VectorCopy( gridSize, oldGridSize ); + for ( i = 0; i < 3; i++ ) + gridSize[ i ] = gridSize[ i ] >= 8.0f ? floor( gridSize[ i ] ) : 8.0f; + + /* ydnar: increase gridSize until grid count is smaller than max allowed */ + numRawGridPoints = MAX_MAP_LIGHTGRID + 1; + j = 0; + while ( numRawGridPoints > MAX_MAP_LIGHTGRID ) + { + /* get world bounds */ + for ( i = 0; i < 3; i++ ) + { + gridMins[ i ] = gridSize[ i ] * ceil( bspModels[ 0 ].mins[ i ] / gridSize[ i ] ); + maxs[ i ] = gridSize[ i ] * floor( bspModels[ 0 ].maxs[ i ] / gridSize[ i ] ); + gridBounds[ i ] = ( maxs[ i ] - gridMins[ i ] ) / gridSize[ i ] + 1; + } + + /* set grid size */ + numRawGridPoints = gridBounds[ 0 ] * gridBounds[ 1 ] * gridBounds[ 2 ]; + + /* increase grid size a bit */ + if ( numRawGridPoints > MAX_MAP_LIGHTGRID ) { + gridSize[ j++ % 3 ] += 16.0f; + } + } + + /* print it */ + Sys_Printf( "Grid size = { %1.0f, %1.0f, %1.0f }\n", gridSize[ 0 ], gridSize[ 1 ], gridSize[ 2 ] ); + + /* different? */ + if ( !VectorCompare( gridSize, oldGridSize ) ) { + sprintf( temp, "%.0f %.0f %.0f", gridSize[ 0 ], gridSize[ 1 ], gridSize[ 2 ] ); + SetKeyValue( &entities[ 0 ], "gridsize", (const char*) temp ); + Sys_FPrintf( SYS_VRB, "Storing adjusted grid size\n" ); + } + + /* 2nd variable. fixme: is this silly? */ + numBSPGridPoints = numRawGridPoints; + + /* allocate lightgrid */ + rawGridPoints = safe_malloc( numRawGridPoints * sizeof( *rawGridPoints ) ); + memset( rawGridPoints, 0, numRawGridPoints * sizeof( *rawGridPoints ) ); + + if ( bspGridPoints != NULL ) { + free( bspGridPoints ); + } + bspGridPoints = safe_malloc( numBSPGridPoints * sizeof( *bspGridPoints ) ); + memset( bspGridPoints, 0, numBSPGridPoints * sizeof( *bspGridPoints ) ); + + /* clear lightgrid */ + for ( i = 0; i < numRawGridPoints; i++ ) + { + VectorCopy( ambientColor, rawGridPoints[ i ].ambient[ j ] ); + rawGridPoints[ i ].styles[ 0 ] = LS_NORMAL; + bspGridPoints[ i ].styles[ 0 ] = LS_NORMAL; + for ( j = 1; j < MAX_LIGHTMAPS; j++ ) + { + rawGridPoints[ i ].styles[ j ] = LS_NONE; + bspGridPoints[ i ].styles[ j ] = LS_NONE; + } + } + + /* note it */ + Sys_Printf( "%9d grid points\n", numRawGridPoints ); +} + + + +/* + LightWorld() + does what it says... + */ + +void LightWorld( const char *BSPFilePath, qboolean fastAllocate ){ + vec3_t color; + float f; + int b, bt; + qboolean minVertex, minGrid; + const char *value; + + + /* ydnar: smooth normals */ + if ( shade ) { + Sys_Printf( "--- SmoothNormals ---\n" ); + SmoothNormals(); + } + + /* determine the number of grid points */ + Sys_Printf( "--- SetupGrid ---\n" ); + SetupGrid(); + + /* find the optional minimum lighting values */ + GetVectorForKey( &entities[ 0 ], "_color", color ); + if ( VectorLength( color ) == 0.0f ) { + VectorSet( color, 1.0, 1.0, 1.0 ); + } + + if ( colorsRGB ) { + color[0] = Image_LinearFloatFromsRGBFloat( color[0] ); + color[1] = Image_LinearFloatFromsRGBFloat( color[1] ); + color[2] = Image_LinearFloatFromsRGBFloat( color[2] ); + } + + /* ambient */ + f = FloatForKey( &entities[ 0 ], "_ambient" ); + if ( f == 0.0f ) { + f = FloatForKey( &entities[ 0 ], "ambient" ); + } + VectorScale( color, f, ambientColor ); + +#ifdef VERTEXLIGHT + /* minvertexlight */ + minVertex = qfalse; + value = ValueForKey( &entities[ 0 ], "_minvertexlight" ); + if ( value[ 0 ] != '\0' ) { + minVertex = qtrue; + f = atof( value ); + VectorScale( color, f, minVertexLight ); + } +#endif + + /* mingridlight */ + minGrid = qfalse; + value = ValueForKey( &entities[ 0 ], "_mingridlight" ); + if ( value[ 0 ] != '\0' ) { + minGrid = qtrue; + f = atof( value ); + VectorScale( color, f, minGridLight ); + } + + /* minlight */ + value = ValueForKey( &entities[ 0 ], "_minlight" ); + if ( value[ 0 ] != '\0' ) { + f = atof( value ); + VectorScale( color, f, minLight ); + if ( minVertex == qfalse ) { + VectorScale( color, f, minVertexLight ); + } + if ( minGrid == qfalse ) { + VectorScale( color, f, minGridLight ); + } + } + + value = ValueForKey( &entities[ 0 ], "_lightscale" ); + if ( value[ 0 ] != '\0' ) { + f = atof( value ); + /*pointScale *= f; + spotScale *= f; + areaScale *= f; + skyScale *= f; + bounceScale *= f;*/ + color[0] *= f; + color[1] *= f; + color[2] *= f; + } + + /* create world lights */ + Sys_FPrintf( SYS_VRB, "--- CreateLights ---\n" ); + CreateEntityLights(); + CreateSurfaceLights(); + Sys_Printf( "%9d point lights\n", numPointLights ); + Sys_Printf( "%9d spotlights\n", numSpotLights ); + Sys_Printf( "%9d diffuse (area) lights\n", numDiffuseLights ); + Sys_Printf( "%9d sun/sky lights\n", numSunLights ); + + /* calculate lightgrid */ + if ( !noGridLighting ) { + /* ydnar: set up light envelopes */ + SetupEnvelopes( qtrue, fastgrid ); + + Sys_Printf( "--- TraceGrid ---\n" ); + inGrid = qtrue; + RunThreadsOnIndividual( numRawGridPoints, qtrue, TraceGrid ); + inGrid = qfalse; + Sys_Printf( "%d x %d x %d = %d grid\n", + gridBounds[ 0 ], gridBounds[ 1 ], gridBounds[ 2 ], numBSPGridPoints ); + + /* ydnar: emit statistics on light culling */ + Sys_FPrintf( SYS_VRB, "%9d grid points envelope culled\n", gridEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d grid points bounds culled\n", gridBoundsCulled ); + } + + /* slight optimization to remove a sqrt */ + subdivideThreshold *= subdivideThreshold; + + /* map the world luxels */ + Sys_Printf( "--- MapRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, MapRawLightmap ); + Sys_Printf( "%9d luxels\n", numLuxels ); + Sys_Printf( "%9d luxels mapped\n", numLuxelsMapped ); + Sys_Printf( "%9d luxels occluded\n", numLuxelsOccluded ); + + /* dirty them up */ + if ( dirty ) { + Sys_Printf( "--- DirtyRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, DirtyRawLightmap ); + } + + /* floodlight pass */ + FloodlightRawLightmaps(); + + /* ydnar: set up light envelopes */ + SetupEnvelopes( qfalse, fast ); + + /* light up my world */ + lightsPlaneCulled = 0; + lightsEnvelopeCulled = 0; + lightsBoundsCulled = 0; + lightsClusterCulled = 0; + + Sys_Printf( "--- IlluminateRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, IlluminateRawLightmap ); + Sys_Printf( "%9d luxels illuminated\n", numLuxelsIlluminated ); + + StitchSurfaceLightmaps(); + +#ifdef VERTEXLIGHT + Sys_Printf( "--- IlluminateVertexes ---\n" ); + RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, IlluminateVertexes ); + Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); +#endif + + /* ydnar: emit statistics on light culling */ + Sys_FPrintf( SYS_VRB, "%9d lights plane culled\n", lightsPlaneCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights envelope culled\n", lightsEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights bounds culled\n", lightsBoundsCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights cluster culled\n", lightsClusterCulled ); + + /* radiosity */ + b = 1; + bt = bounce; + while ( bounce > 0 ) + { + /* store off the bsp between bounces */ + StoreSurfaceLightmaps( fastAllocate ); + UnparseEntities(); + Sys_Printf( "Writing %s\n", BSPFilePath ); + WriteBSPFile( BSPFilePath ); + + /* note it */ + Sys_Printf( "\n--- Radiosity (bounce %d of %d) ---\n", b, bt ); + + /* flag bouncing */ + bouncing = qtrue; + VectorClear( ambientColor ); + floodlighty = qfalse; + + /* generate diffuse lights */ + RadFreeLights(); + RadCreateDiffuseLights(); + + /* setup light envelopes */ + SetupEnvelopes( qfalse, fastbounce ); + if ( numLights == 0 ) { + Sys_Printf( "No diffuse light to calculate, ending radiosity.\n" ); + return; + } + + /* add to lightgrid */ + if ( bouncegrid ) { + gridEnvelopeCulled = 0; + gridBoundsCulled = 0; + + Sys_Printf( "--- BounceGrid ---\n" ); + inGrid = qtrue; + RunThreadsOnIndividual( numRawGridPoints, qtrue, TraceGrid ); + inGrid = qfalse; + Sys_FPrintf( SYS_VRB, "%9d grid points envelope culled\n", gridEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d grid points bounds culled\n", gridBoundsCulled ); + } + + /* light up my world */ + lightsPlaneCulled = 0; + lightsEnvelopeCulled = 0; + lightsBoundsCulled = 0; + lightsClusterCulled = 0; + + Sys_Printf( "--- IlluminateRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, IlluminateRawLightmap ); + Sys_Printf( "%9d luxels illuminated\n", numLuxelsIlluminated ); + Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); + + StitchSurfaceLightmaps(); + +#ifdef VERTEXLIGHT + Sys_Printf( "--- IlluminateVertexes ---\n" ); + RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, IlluminateVertexes ); + Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); +#endif + + /* ydnar: emit statistics on light culling */ + Sys_FPrintf( SYS_VRB, "%9d lights plane culled\n", lightsPlaneCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights envelope culled\n", lightsEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights bounds culled\n", lightsBoundsCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights cluster culled\n", lightsClusterCulled ); + + /* interate */ + bounce--; + b++; + } + /* ydnar: store off lightmaps */ + StoreSurfaceLightmaps( fastAllocate ); +} + + + +/* + LightMain() + main routine for light processing + */ + +int LightMain( int argc, char **argv ){ + int i; + float f; + char BSPFilePath[ 1024 ]; + char surfaceFilePath[ 1024 ]; + BSPFilePath[0] = 0; + surfaceFilePath[0] = 0; + const char *value; + int lightmapMergeSize = 0; + qboolean lightSamplesInsist = qfalse; + qboolean fastAllocate = qfalse; + + /* note it */ + Sys_Printf( "--- Light ---\n" ); + Sys_Printf( "--- ProcessGameSpecific ---\n" ); + + /* set standard game flags */ + wolfLight = game->wolfLight; + if ( wolfLight == qtrue ) { + Sys_Printf( " lighting model: wolf\n" ); + } + else{ + Sys_Printf( " lighting model: quake3\n" ); + } + + lmCustomSize = game->lightmapSize; + Sys_Printf( " lightmap size: %d x %d pixels\n", lmCustomSize, lmCustomSize ); + + lightmapGamma = game->lightmapGamma; + Sys_Printf( " lighting gamma: %f\n", lightmapGamma ); + + lightmapsRGB = game->lightmapsRGB; + if ( lightmapsRGB ) { + Sys_Printf( " lightmap colorspace: sRGB\n" ); + } + else{ + Sys_Printf( " lightmap colorspace: linear\n" ); + } + + texturesRGB = game->texturesRGB; + if ( texturesRGB ) { + Sys_Printf( " texture colorspace: sRGB\n" ); + } + else{ + Sys_Printf( " texture colorspace: linear\n" ); + } + + colorsRGB = game->colorsRGB; + if ( colorsRGB ) { + Sys_Printf( " _color colorspace: sRGB\n" ); + } + else{ + Sys_Printf( " _color colorspace: linear\n" ); + } + + lightmapCompensate = game->lightmapCompensate; + Sys_Printf( " lighting compensation: %f\n", lightmapCompensate ); + + lightmapExposure = game->lightmapExposure; + Sys_Printf( " lighting exposure: %f\n", lightmapExposure ); + + gridScale = game->gridScale; + Sys_Printf( " lightgrid scale: %f\n", gridScale ); + + gridAmbientScale = game->gridAmbientScale; + Sys_Printf( " lightgrid ambient scale: %f\n", gridAmbientScale ); + + lightAngleHL = game->lightAngleHL; + if ( lightAngleHL ) { + Sys_Printf( " half lambert light angle attenuation enabled \n" ); + } + + noStyles = game->noStyles; + if ( noStyles == qtrue ) { + Sys_Printf( " shader lightstyles hack: disabled\n" ); + } + else{ + Sys_Printf( " shader lightstyles hack: enabled\n" ); + } + + patchShadows = game->patchShadows; + if ( patchShadows == qtrue ) { + Sys_Printf( " patch shadows: enabled\n" ); + } + else{ + Sys_Printf( " patch shadows: disabled\n" ); + } + + deluxemap = game->deluxeMap; + deluxemode = game->deluxeMode; + if ( deluxemap == qtrue ) { + if ( deluxemode ) { + Sys_Printf( " deluxemapping: enabled with tangentspace deluxemaps\n" ); + } + else{ + Sys_Printf( " deluxemapping: enabled with modelspace deluxemaps\n" ); + } + } + else{ + Sys_Printf( " deluxemapping: disabled\n" ); + } + + Sys_Printf( "--- ProcessCommandLine ---\n" ); + + /* process commandline arguments */ + for ( i = 1; i < ( argc - 1 ); i++ ) + { + /* lightsource scaling */ + if ( !strcmp( argv[ i ], "-point" ) || !strcmp( argv[ i ], "-pointscale" ) ) { + f = atof( argv[ i + 1 ] ); + pointScale *= f; + spotScale *= f; + Sys_Printf( "Spherical point (entity) light scaled by %f to %f\n", f, pointScale ); + Sys_Printf( "Spot point (entity) light scaled by %f to %f\n", f, spotScale ); + i++; + } + + else if ( !strcmp( argv[ i ], "-spherical" ) || !strcmp( argv[ i ], "-sphericalscale" ) ) { + f = atof( argv[ i + 1 ] ); + pointScale *= f; + Sys_Printf( "Spherical point (entity) light scaled by %f to %f\n", f, pointScale ); + i++; + } + + else if ( !strcmp( argv[ i ], "-spot" ) || !strcmp( argv[ i ], "-spotscale" ) ) { + f = atof( argv[ i + 1 ] ); + spotScale *= f; + Sys_Printf( "Spot point (entity) light scaled by %f to %f\n", f, spotScale ); + i++; + } + + else if ( !strcmp( argv[ i ], "-area" ) || !strcmp( argv[ i ], "-areascale" ) ) { + f = atof( argv[ i + 1 ] ); + areaScale *= f; + Sys_Printf( "Area (shader) light scaled by %f to %f\n", f, areaScale ); + i++; + } + + else if ( !strcmp( argv[ i ], "-sky" ) || !strcmp( argv[ i ], "-skyscale" ) ) { + f = atof( argv[ i + 1 ] ); + skyScale *= f; + Sys_Printf( "Sky/sun light scaled by %f to %f\n", f, skyScale ); + i++; + } + + else if ( !strcmp( argv[ i ], "-bouncescale" ) ) { + f = atof( argv[ i + 1 ] ); + bounceScale *= f; + Sys_Printf( "Bounce (radiosity) light scaled by %f to %f\n", f, bounceScale ); + i++; + } + + else if ( !strcmp( argv[ i ], "-scale" ) ) { + f = atof( argv[ i + 1 ] ); + pointScale *= f; + spotScale *= f; + areaScale *= f; + skyScale *= f; + bounceScale *= f; + Sys_Printf( "All light scaled by %f\n", f ); + i++; + } + + else if ( !strcmp( argv[ i ], "-gridscale" ) ) { + f = atof( argv[ i + 1 ] ); + Sys_Printf( "Grid lighting scaled by %f\n", f ); + gridScale *= f; + i++; + } + + else if ( !strcmp( argv[ i ], "-gridambientscale" ) ) { + f = atof( argv[ i + 1 ] ); + Sys_Printf( "Grid ambient lighting scaled by %f\n", f ); + gridAmbientScale *= f; + i++; + } + + else if ( !strcmp( argv[ i ], "-griddirectionality" ) ) { + f = atof( argv[ i + 1 ] ); + if ( f > 1 ) { + f = 1; + } + if ( f < gridAmbientDirectionality ) { + gridAmbientDirectionality = f; + } + Sys_Printf( "Grid directionality is %f\n", f ); + gridDirectionality = f; + i++; + } + + else if ( !strcmp( argv[ i ], "-gridambientdirectionality" ) ) { + f = atof( argv[ i + 1 ] ); + if ( f < -1 ) { + f = -1; + } + if ( f > gridDirectionality ) { + gridDirectionality = f; + } + Sys_Printf( "Grid ambient directionality is %f\n", f ); + gridAmbientDirectionality = f; + i++; + } + + else if ( !strcmp( argv[ i ], "-gamma" ) ) { + f = atof( argv[ i + 1 ] ); + lightmapGamma = f; + Sys_Printf( "Lighting gamma set to %f\n", lightmapGamma ); + i++; + } + + else if ( !strcmp( argv[ i ], "-sRGBlight" ) ) { + lightmapsRGB = qtrue; + Sys_Printf( "Lighting is in sRGB\n" ); + } + + else if ( !strcmp( argv[ i ], "-nosRGBlight" ) ) { + lightmapsRGB = qfalse; + Sys_Printf( "Lighting is linear\n" ); + } + + else if ( !strcmp( argv[ i ], "-sRGBtex" ) ) { + texturesRGB = qtrue; + Sys_Printf( "Textures are in sRGB\n" ); + } + + else if ( !strcmp( argv[ i ], "-nosRGBtex" ) ) { + texturesRGB = qfalse; + Sys_Printf( "Textures are linear\n" ); + } + + else if ( !strcmp( argv[ i ], "-sRGBcolor" ) ) { + colorsRGB = qtrue; + Sys_Printf( "Colors are in sRGB\n" ); + } + + else if ( !strcmp( argv[ i ], "-nosRGBcolor" ) ) { + colorsRGB = qfalse; + Sys_Printf( "Colors are linear\n" ); + } + + else if ( !strcmp( argv[ i ], "-sRGB" ) ) { + lightmapsRGB = qtrue; + Sys_Printf( "Lighting is in sRGB\n" ); + texturesRGB = qtrue; + Sys_Printf( "Textures are in sRGB\n" ); + colorsRGB = qtrue; + Sys_Printf( "Colors are in sRGB\n" ); + } + + else if ( !strcmp( argv[ i ], "-nosRGB" ) ) { + lightmapsRGB = qfalse; + Sys_Printf( "Lighting is linear\n" ); + texturesRGB = qfalse; + Sys_Printf( "Textures are linear\n" ); + colorsRGB = qfalse; + Sys_Printf( "Colors are linear\n" ); + } + + else if ( !strcmp( argv[ i ], "-exposure" ) ) { + f = atof( argv[ i + 1 ] ); + lightmapExposure = f; + Sys_Printf( "Lighting exposure set to %f\n", lightmapExposure ); + i++; + } + + else if ( !strcmp( argv[ i ], "-compensate" ) ) { + f = atof( argv[ i + 1 ] ); + if ( f <= 0.0f ) { + f = 1.0f; + } + lightmapCompensate = f; + Sys_Printf( "Lighting compensation set to 1/%f\n", lightmapCompensate ); + i++; + } + + /* ydnar switches */ + else if ( !strcmp( argv[ i ], "-bounce" ) ) { + bounce = atoi( argv[ i + 1 ] ); + if ( bounce < 0 ) { + bounce = 0; + } + else if ( bounce > 0 ) { + Sys_Printf( "Radiosity enabled with %d bounce(s)\n", bounce ); + } + i++; + } + + else if ( !strcmp( argv[ i ], "-supersample" ) || !strcmp( argv[ i ], "-super" ) ) { + superSample = atoi( argv[ i + 1 ] ); + if ( superSample < 1 ) { + superSample = 1; + } + else if ( superSample > 1 ) { + Sys_Printf( "Ordered-grid supersampling enabled with %d sample(s) per lightmap texel\n", ( superSample * superSample ) ); + } + i++; + } + + else if ( !strcmp( argv[ i ], "-randomsamples" ) ) { + lightRandomSamples = qtrue; + Sys_Printf( "Random sampling enabled\n", lightRandomSamples ); + } + + else if ( !strcmp( argv[ i ], "-samples" ) ) { + if ( *argv[i + 1] == '+' ) { + lightSamplesInsist = qtrue; + } + else{ + lightSamplesInsist = qfalse; + } + lightSamples = atoi( argv[ i + 1 ] ); + if ( lightSamples < 1 ) { + lightSamples = 1; + } + else if ( lightSamples > 1 ) { + Sys_Printf( "Adaptive supersampling enabled with %d sample(s) per lightmap texel\n", lightSamples ); + } + i++; + } + + else if ( !strcmp( argv[ i ], "-samplessearchboxsize" ) ) { + lightSamplesSearchBoxSize = atoi( argv[ i + 1 ] ); + if ( lightSamplesSearchBoxSize <= 0 ) { + lightSamplesSearchBoxSize = 1; + } + if ( lightSamplesSearchBoxSize > 4 ) { + lightSamplesSearchBoxSize = 4; /* more makes no sense */ + } + else if ( lightSamplesSearchBoxSize != 1 ) { + Sys_Printf( "Adaptive supersampling uses %f times the normal search box size\n", lightSamplesSearchBoxSize ); + } + i++; + } + + else if ( !strcmp( argv[ i ], "-filter" ) ) { + filter = qtrue; + Sys_Printf( "Lightmap filtering enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-dark" ) ) { + dark = qtrue; + Sys_Printf( "Dark lightmap seams enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-shadeangle" ) ) { + shadeAngleDegrees = atof( argv[ i + 1 ] ); + if ( shadeAngleDegrees < 0.0f ) { + shadeAngleDegrees = 0.0f; + } + else if ( shadeAngleDegrees > 0.0f ) { + shade = qtrue; + Sys_Printf( "Phong shading enabled with a breaking angle of %f degrees\n", shadeAngleDegrees ); + } + i++; + } + + else if ( !strcmp( argv[ i ], "-thresh" ) ) { + subdivideThreshold = atof( argv[ i + 1 ] ); + if ( subdivideThreshold < 0 ) { + subdivideThreshold = DEFAULT_SUBDIVIDE_THRESHOLD; + } + else{ + Sys_Printf( "Subdivision threshold set at %.3f\n", subdivideThreshold ); + } + i++; + } + + else if ( !strcmp( argv[ i ], "-approx" ) ) { + approximateTolerance = atoi( argv[ i + 1 ] ); + if ( approximateTolerance < 0 ) { + approximateTolerance = 0; + } + else if ( approximateTolerance > 0 ) { + Sys_Printf( "Approximating lightmaps within a byte tolerance of %d\n", approximateTolerance ); + } + i++; + } + + else if ( !strcmp( argv[ i ], "-deluxe" ) || !strcmp( argv[ i ], "-deluxemap" ) ) { + deluxemap = qtrue; + Sys_Printf( "Generating deluxemaps for average light direction\n" ); + } + else if ( !strcmp( argv[ i ], "-deluxemode" ) ) { + deluxemode = atoi( argv[ i + 1 ] ); + if ( deluxemode == 0 || deluxemode > 1 || deluxemode < 0 ) { + Sys_Printf( "Generating modelspace deluxemaps\n" ); + deluxemode = 0; + } + else{ + Sys_Printf( "Generating tangentspace deluxemaps\n" ); + } + i++; + } + else if ( !strcmp( argv[ i ], "-nodeluxe" ) || !strcmp( argv[ i ], "-nodeluxemap" ) ) { + deluxemap = qfalse; + Sys_Printf( "Disabling generating of deluxemaps for average light direction\n" ); + } + else if ( !strcmp( argv[ i ], "-external" ) ) { + externalLightmaps = qtrue; + Sys_Printf( "Storing all lightmaps externally\n" ); + } + else if ( !strcmp( argv[ i ], "-externalhdr" ) ) { + externalLightmaps = qtrue; + externalHDRLightmaps = qtrue; + Sys_Printf( "Storing all hdr lightmaps externally\n" ); + } + + else if ( !strcmp( argv[ i ], "-lightmapsize" ) ) { + lmCustomSize = atoi( argv[ i + 1 ] ); + + /* must be a power of 2 and greater than 2 */ + if ( ( ( lmCustomSize - 1 ) & lmCustomSize ) || lmCustomSize < 2 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Lightmap size must be a power of 2, greater or equal to 2 pixels.\n" ); + lmCustomSize = game->lightmapSize; + } + i++; + Sys_Printf( "Default lightmap size set to %d x %d pixels\n", lmCustomSize, lmCustomSize ); + + /* enable external lightmaps */ + if ( lmCustomSize != game->lightmapSize && !externalLightmaps) { + externalLightmaps = qtrue; + Sys_Printf( "Storing all lightmaps externally\n" ); + } + } + + else if ( !strcmp( argv[ i ], "-rawlightmapsizelimit" ) ) { + lmLimitSize = atoi( argv[ i + 1 ] ); + + i++; + Sys_Printf( "Raw lightmap size limit set to %d x %d pixels\n", lmLimitSize, lmLimitSize ); + } + + else if ( !strcmp( argv[ i ], "-lightmapdir" ) ) { + lmCustomDir = argv[i + 1]; + i++; + Sys_Printf( "Lightmap directory set to %s\n", lmCustomDir ); + externalLightmaps = qtrue; + Sys_Printf( "Storing all lightmaps externally\n" ); + } + + /* ydnar: add this to suppress warnings */ + else if ( !strcmp( argv[ i ], "-custinfoparms" ) ) { + Sys_Printf( "Custom info parms enabled\n" ); + useCustomInfoParms = qtrue; + } + + else if ( !strcmp( argv[ i ], "-wolf" ) ) { + /* -game should already be set */ + wolfLight = qtrue; + Sys_Printf( "Enabling Wolf lighting model (linear default)\n" ); + } + + else if ( !strcmp( argv[ i ], "-q3" ) ) { + /* -game should already be set */ + wolfLight = qfalse; + Sys_Printf( "Enabling Quake 3 lighting model (nonlinear default)\n" ); + } + + else if ( !strcmp( argv[ i ], "-extradist" ) ) { + extraDist = atof( argv[ i + 1 ] ); + if ( extraDist < 0 ) { + extraDist = 0; + } + i++; + Sys_Printf( "Default extra radius set to %f units\n", extraDist ); + } + + else if ( !strcmp( argv[ i ], "-sunonly" ) ) { + sunOnly = qtrue; + Sys_Printf( "Only computing sunlight\n" ); + } + + else if ( !strcmp( argv[ i ], "-bounceonly" ) ) { + bounceOnly = qtrue; + Sys_Printf( "Storing bounced light (radiosity) only\n" ); + } + + else if ( !strcmp( argv[ i ], "-nocollapse" ) ) { + noCollapse = qtrue; + Sys_Printf( "Identical lightmap collapsing disabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-nolightmapsearch" ) ) { + lightmapSearchBlockSize = 1; + Sys_Printf( "No lightmap searching - all lightmaps will be sequential\n" ); + } + + else if ( !strcmp( argv[ i ], "-lightmapsearchpower" ) ) { + lightmapMergeSize = ( game->lightmapSize << atoi( argv[i + 1] ) ); + ++i; + Sys_Printf( "Restricted lightmap searching enabled - optimize for lightmap merge power %d (size %d)\n", atoi( argv[i] ), lightmapMergeSize ); + } + + else if ( !strcmp( argv[ i ], "-lightmapsearchblocksize" ) ) { + lightmapSearchBlockSize = atoi( argv[i + 1] ); + ++i; + Sys_Printf( "Restricted lightmap searching enabled - block size set to %d\n", lightmapSearchBlockSize ); + } + + else if ( !strcmp( argv[ i ], "-shade" ) ) { + shade = qtrue; + Sys_Printf( "Phong shading enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-bouncegrid" ) ) { + bouncegrid = qtrue; + if ( bounce > 0 ) { + Sys_Printf( "Grid lighting with radiosity enabled\n" ); + } + } + + else if ( !strcmp( argv[ i ], "-smooth" ) ) { + lightSamples = EXTRA_SCALE; + Sys_Printf( "The -smooth argument is deprecated, use \"-samples 2\" instead\n" ); + } + + else if ( !strcmp( argv[ i ], "-nofastpoint" ) ) { + fastpoint = qfalse; + Sys_Printf( "Automatic fast mode for point lights disabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-fast" ) ) { + fast = qtrue; + fastgrid = qtrue; + fastbounce = qtrue; + Sys_Printf( "Fast mode enabled for all area lights\n" ); + } + + else if ( !strcmp( argv[ i ], "-faster" ) ) { + faster = qtrue; + fast = qtrue; + fastgrid = qtrue; + fastbounce = qtrue; + Sys_Printf( "Faster mode enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-fastallocate" ) ) { + fastAllocate = qtrue; + Sys_Printf( "Fast allocation mode enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-fastgrid" ) ) { + fastgrid = qtrue; + Sys_Printf( "Fast grid lighting enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-fastbounce" ) ) { + fastbounce = qtrue; + Sys_Printf( "Fast bounce mode enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-cheap" ) ) { + cheap = qtrue; + cheapgrid = qtrue; + Sys_Printf( "Cheap mode enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-cheapgrid" ) ) { + cheapgrid = qtrue; + Sys_Printf( "Cheap grid mode enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-normalmap" ) ) { + normalmap = qtrue; + Sys_Printf( "Storing normal map instead of lightmap\n" ); + } + + else if ( !strcmp( argv[ i ], "-trisoup" ) ) { + trisoup = qtrue; + Sys_Printf( "Converting brush faces to triangle soup\n" ); + } + + else if ( !strcmp( argv[ i ], "-debug" ) ) { + debug = qtrue; + Sys_Printf( "Lightmap debugging enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-debugsurfaces" ) || !strcmp( argv[ i ], "-debugsurface" ) ) { + debugSurfaces = qtrue; + Sys_Printf( "Lightmap surface debugging enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-debugunused" ) ) { + debugUnused = qtrue; + Sys_Printf( "Unused luxel debugging enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-debugaxis" ) ) { + debugAxis = qtrue; + Sys_Printf( "Lightmap axis debugging enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-debugcluster" ) ) { + debugCluster = qtrue; + Sys_Printf( "Luxel cluster debugging enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-debugorigin" ) ) { + debugOrigin = qtrue; + Sys_Printf( "Luxel origin debugging enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-debugdeluxe" ) ) { + deluxemap = qtrue; + debugDeluxemap = qtrue; + Sys_Printf( "Deluxemap debugging enabled\n" ); + } + + else if ( !strcmp( argv[ i ], "-export" ) ) { + exportLightmaps = qtrue; + Sys_Printf( "Exporting lightmaps\n" ); + } + + else if ( !strcmp( argv[ i ], "-notrace" ) ) { + noTrace = qtrue; + Sys_Printf( "Shadow occlusion disabled\n" ); + } + else if ( !strcmp( argv[ i ], "-patchshadows" ) ) { + patchShadows = qtrue; + Sys_Printf( "Patch shadow casting enabled\n" ); + } + else if ( !strcmp( argv[ i ], "-extra" ) ) { + superSample = EXTRA_SCALE; /* ydnar */ + Sys_Printf( "The -extra argument is deprecated, use \"-super 2\" instead\n" ); + } + else if ( !strcmp( argv[ i ], "-extrawide" ) ) { + superSample = EXTRAWIDE_SCALE; /* ydnar */ + filter = qtrue; /* ydnar */ + Sys_Printf( "The -extrawide argument is deprecated, use \"-filter [-super 2]\" instead\n" ); + } + else if ( !strcmp( argv[ i ], "-samplesize" ) ) { + sampleSize = atoi( argv[ i + 1 ] ); + if ( sampleSize < 1 ) { + sampleSize = 1; + } + i++; + Sys_Printf( "Default lightmap sample size set to %dx%d units\n", sampleSize, sampleSize ); + } + else if ( !strcmp( argv[ i ], "-minsamplesize" ) ) { + minSampleSize = atoi( argv[ i + 1 ] ); + if ( minSampleSize < 1 ) { + minSampleSize = 1; + } + i++; + Sys_Printf( "Minimum lightmap sample size set to %dx%d units\n", minSampleSize, minSampleSize ); + } + else if ( !strcmp( argv[ i ], "-samplescale" ) ) { + sampleScale = atoi( argv[ i + 1 ] ); + i++; + Sys_Printf( "Lightmaps sample scale set to %d\n", sampleScale ); + } +#ifdef VERTEXLIGHT + else if ( !strcmp( argv[ i ], "-novertex" ) ) { + noVertexLighting = qtrue; + Sys_Printf( "Disabling vertex lighting\n" ); + } +#endif + else if ( !strcmp( argv[ i ], "-nogrid" ) ) { + noGridLighting = qtrue; + Sys_Printf( "Disabling grid lighting\n" ); + } + else if ( !strcmp( argv[ i ], "-border" ) ) { + lightmapBorder = qtrue; + Sys_Printf( "Adding debug border to lightmaps\n" ); + } + else if ( !strcmp( argv[ i ], "-nosurf" ) ) { + noSurfaces = qtrue; + Sys_Printf( "Not tracing against surfaces\n" ); + } + else if ( !strcmp( argv[ i ], "-dump" ) ) { + dump = qtrue; + Sys_Printf( "Dumping radiosity lights into numbered prefabs\n" ); + } + else if ( !strcmp( argv[ i ], "-lomem" ) ) { + loMem = qtrue; + Sys_Printf( "Enabling low-memory (potentially slower) lighting mode\n" ); + } + else if ( !strcmp( argv[ i ], "-lightsubdiv" ) ) { + defaultLightSubdivide = atoi( argv[ i + 1 ] ); + if ( defaultLightSubdivide < 1 ) { + defaultLightSubdivide = 1; + } + i++; + Sys_Printf( "Default light subdivision set to %d\n", defaultLightSubdivide ); + } + else if ( !strcmp( argv[ i ], "-lightanglehl" ) ) { + if ( ( atoi( argv[ i + 1 ] ) != 0 ) != lightAngleHL ) { + lightAngleHL = ( atoi( argv[ i + 1 ] ) != 0 ); + if ( lightAngleHL ) { + Sys_Printf( "Enabling half lambert light angle attenuation\n" ); + } + else{ + Sys_Printf( "Disabling half lambert light angle attenuation\n" ); + } + } + i++; + } + else if ( !strcmp( argv[ i ], "-nostyle" ) || !strcmp( argv[ i ], "-nostyles" ) ) { + noStyles = qtrue; + Sys_Printf( "Disabling lightstyles\n" ); + } + else if ( !strcmp( argv[ i ], "-style" ) || !strcmp( argv[ i ], "-styles" ) ) { + noStyles = qfalse; + Sys_Printf( "Enabling lightstyles\n" ); + } + else if ( !strcmp( argv[ i ], "-cpma" ) ) { + cpmaHack = qtrue; + Sys_Printf( "Enabling Challenge Pro Mode Asstacular Vertex Lighting Mode (tm)\n" ); + } + else if ( !strcmp( argv[ i ], "-floodlight" ) ) { + floodlighty = qtrue; + Sys_Printf( "FloodLighting enabled\n" ); + } + else if ( !strcmp( argv[ i ], "-debugnormals" ) ) { + debugnormals = qtrue; + Sys_Printf( "DebugNormals enabled\n" ); + } + else if ( !strcmp( argv[ i ], "-lowquality" ) ) { + floodlight_lowquality = qtrue; + Sys_Printf( "Low Quality FloodLighting enabled\n" ); + } + + /* r7: dirtmapping */ + else if ( !strcmp( argv[ i ], "-dirty" ) ) { + dirty = qtrue; + Sys_Printf( "Dirtmapping enabled\n" ); + } + else if ( !strcmp( argv[ i ], "-dirtdebug" ) || !strcmp( argv[ i ], "-debugdirt" ) ) { + dirtDebug = qtrue; + Sys_Printf( "Dirtmap debugging enabled\n" ); + } + else if ( !strcmp( argv[ i ], "-dirtmode" ) ) { + dirtMode = atoi( argv[ i + 1 ] ); + if ( dirtMode != 0 && dirtMode != 1 ) { + dirtMode = 0; + } + if ( dirtMode == 1 ) { + Sys_Printf( "Enabling randomized dirtmapping\n" ); + } + else{ + Sys_Printf( "Enabling ordered dir mapping\n" ); + } + i++; + } + else if ( !strcmp( argv[ i ], "-dirtdepth" ) ) { + dirtDepth = atof( argv[ i + 1 ] ); + if ( dirtDepth <= 0.0f ) { + dirtDepth = 128.0f; + } + Sys_Printf( "Dirtmapping depth set to %.1f\n", dirtDepth ); + i++; + } + else if ( !strcmp( argv[ i ], "-dirtscale" ) ) { + dirtScale = atof( argv[ i + 1 ] ); + if ( dirtScale <= 0.0f ) { + dirtScale = 1.0f; + } + Sys_Printf( "Dirtmapping scale set to %.1f\n", dirtScale ); + i++; + } + else if ( !strcmp( argv[ i ], "-dirtgain" ) ) { + dirtGain = atof( argv[ i + 1 ] ); + if ( dirtGain <= 0.0f ) { + dirtGain = 1.0f; + } + Sys_Printf( "Dirtmapping gain set to %.1f\n", dirtGain ); + i++; + } + else if ( !strcmp( argv[ i ], "-trianglecheck" ) ) { + lightmapTriangleCheck = qtrue; + } + else if ( !strcmp( argv[ i ], "-extravisnudge" ) ) { + lightmapExtraVisClusterNudge = qtrue; + } + else if ( !strcmp( argv[ i ], "-fill" ) ) { + lightmapFill = qtrue; + Sys_Printf( "Filling lightmap colors from surrounding pixels to improve JPEG compression\n" ); + } + else if ( !strcmp( argv[ i ], "-bspfile" ) ) + { + strcpy( BSPFilePath, argv[i + 1] ); + i++; + Sys_Printf( "Use %s as bsp file\n", BSPFilePath ); + } + else if ( !strcmp( argv[ i ], "-srffile" ) ) + { + strcpy( surfaceFilePath, argv[i + 1] ); + i++; + Sys_Printf( "Use %s as surface file\n", surfaceFilePath ); + } + /* unhandled args */ + else + { + Sys_FPrintf( SYS_WRN, "WARNING: Unknown argument \"%s\"\n", argv[ i ] ); + } + + } + + /* fix up falloff tolerance for sRGB */ + if ( lightmapsRGB ) { + falloffTolerance = Image_LinearFloatFromsRGBFloat( falloffTolerance * ( 1.0 / 255.0 ) ) * 255.0; + } + + /* fix up samples count */ + if ( lightRandomSamples ) { + if ( !lightSamplesInsist ) { + /* approximately match -samples in quality */ + switch ( lightSamples ) + { + /* somewhat okay */ + case 1: + case 2: + lightSamples = 16; + Sys_Printf( "Adaptive supersampling preset enabled with %d random sample(s) per lightmap texel\n", lightSamples ); + break; + + /* good */ + case 3: + lightSamples = 64; + Sys_Printf( "Adaptive supersampling preset enabled with %d random sample(s) per lightmap texel\n", lightSamples ); + break; + + /* perfect */ + case 4: + lightSamples = 256; + Sys_Printf( "Adaptive supersampling preset enabled with %d random sample(s) per lightmap texel\n", lightSamples ); + break; + + default: break; + } + } + } + + /* fix up lightmap search power */ + if ( lightmapMergeSize ) { + lightmapSearchBlockSize = ( lightmapMergeSize / lmCustomSize ) * ( lightmapMergeSize / lmCustomSize ); + if ( lightmapSearchBlockSize < 1 ) { + lightmapSearchBlockSize = 1; + } + + Sys_Printf( "Restricted lightmap searching enabled - block size adjusted to %d\n", lightmapSearchBlockSize ); + } + + strcpy( source, ExpandArg( argv[ i ] ) ); + StripExtension( source ); + DefaultExtension( source, ".map" ); + + if (!BSPFilePath[0]) { + strcpy( BSPFilePath, ExpandArg( argv[ i ] ) ); + StripExtension( BSPFilePath ); + DefaultExtension( BSPFilePath, ".bsp" ); + } + + if (!surfaceFilePath[0]) { + strcpy( surfaceFilePath, ExpandArg( argv[ i ] ) ); + StripExtension( surfaceFilePath ); + DefaultExtension( surfaceFilePath, ".srf" ); + } + + /* ydnar: set default sample size */ + SetDefaultSampleSize( sampleSize ); + + /* ydnar: handle shaders */ + BeginMapShaderFile( BSPFilePath ); + LoadShaderInfo(); + + /* note loading */ + Sys_Printf( "Loading %s\n", source ); + + /* ydnar: load surface file */ + LoadSurfaceExtraFile( surfaceFilePath ); + + /* load bsp file */ + LoadBSPFile( BSPFilePath ); + + /* parse bsp entities */ + ParseEntities(); + + /* inject command line parameters */ + InjectCommandLine( argv, 0, argc - 1 ); + + /* load map file */ + value = ValueForKey( &entities[ 0 ], "_keepLights" ); + if ( value[ 0 ] != '1' ) { + LoadMapFile( source, qtrue, qfalse ); + } + + /* set the entity/model origins and init yDrawVerts */ + SetEntityOrigins(); + + /* ydnar: set up optimization */ + SetupBrushes(); + SetupDirt(); + SetupFloodLight(); + SetupSurfaceLightmaps(); + + /* initialize the surface facet tracing */ + SetupTraceNodes(); + + /* light the world */ + LightWorld( BSPFilePath, fastAllocate ); + + /* write out the bsp */ + UnparseEntities(); + Sys_Printf( "Writing %s\n", BSPFilePath ); + WriteBSPFile( BSPFilePath ); + + /* ydnar: export lightmaps */ + if ( exportLightmaps && !externalLightmaps ) { + ExportLightmaps(); + } + + /* return to sender */ + return 0; +} diff --git a/tools/vmap/light_bounce.c b/tools/vmap/light_bounce.c new file mode 100644 index 0000000..ec59bed --- /dev/null +++ b/tools/vmap/light_bounce.c @@ -0,0 +1,982 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_BOUNCE_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* functions */ + +/* + RadFreeLights() + deletes any existing lights, freeing up memory for the next bounce + */ + +void RadFreeLights( void ){ + light_t *light, *next; + + + /* delete lights */ + for ( light = lights; light; light = next ) + { + next = light->next; + if ( light->w != NULL ) { + FreeWinding( light->w ); + } + free( light ); + } + numLights = 0; + lights = NULL; +} + + + +/* + RadClipWindingEpsilon() + clips a rad winding by a plane + based off the regular clip winding code + */ + +static void RadClipWindingEpsilon( radWinding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, radWinding_t *front, radWinding_t *back, clipWork_t *cw ){ + vec_t *dists; + int *sides; + int counts[ 3 ]; + vec_t dot; /* ydnar: changed from static b/c of threading */ /* VC 4.2 optimizer bug if not static? */ + int i, j, k; + radVert_t *v1, *v2, mid; + int maxPoints; + + + /* crutch */ + dists = cw->dists; + sides = cw->sides; + + /* clear counts */ + counts[ 0 ] = counts[ 1 ] = counts[ 2 ] = 0; + + /* determine sides for each point */ + for ( i = 0; i < in->numVerts; i++ ) + { + dot = DotProduct( in->verts[ i ].xyz, normal ); + dot -= dist; + dists[ i ] = dot; + if ( dot > epsilon ) { + sides[ i ] = SIDE_FRONT; + } + else if ( dot < -epsilon ) { + sides[ i ] = SIDE_BACK; + } + else{ + sides[ i ] = SIDE_ON; + } + counts[ sides[ i ] ]++; + } + sides[ i ] = sides[ 0 ]; + dists[ i ] = dists[ 0 ]; + + /* clear front and back */ + front->numVerts = back->numVerts = 0; + + /* handle all on one side cases */ + if ( counts[ 0 ] == 0 ) { + memcpy( back, in, sizeof( radWinding_t ) ); + return; + } + if ( counts[ 1 ] == 0 ) { + memcpy( front, in, sizeof( radWinding_t ) ); + return; + } + + /* setup windings */ + maxPoints = in->numVerts + 4; + + /* do individual verts */ + for ( i = 0; i < in->numVerts; i++ ) + { + /* do simple vertex copies first */ + v1 = &in->verts[ i ]; + + if ( sides[ i ] == SIDE_ON ) { + memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) ); + memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) ); + continue; + } + + if ( sides[ i ] == SIDE_FRONT ) { + memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) ); + } + + if ( sides[ i ] == SIDE_BACK ) { + memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) ); + } + + if ( sides[ i + 1 ] == SIDE_ON || sides[ i + 1 ] == sides[ i ] ) { + continue; + } + + /* generate a split vertex */ + v2 = &in->verts[ ( i + 1 ) % in->numVerts ]; + + dot = dists[ i ] / ( dists[ i ] - dists[ i + 1 ] ); + + /* average vertex values */ + for ( j = 0; j < 4; j++ ) + { + /* color */ + if ( j < 4 ) { + for ( k = 0; k < MAX_LIGHTMAPS; k++ ) + mid.color[ k ][ j ] = v1->color[ k ][ j ] + dot * ( v2->color[ k ][ j ] - v1->color[ k ][ j ] ); + } + + /* xyz, normal */ + if ( j < 3 ) { + mid.xyz[ j ] = v1->xyz[ j ] + dot * ( v2->xyz[ j ] - v1->xyz[ j ] ); + mid.normal[ j ] = v1->normal[ j ] + dot * ( v2->normal[ j ] - v1->normal[ j ] ); + } + + /* st, lightmap */ + if ( j < 2 ) { + mid.st[ j ] = v1->st[ j ] + dot * ( v2->st[ j ] - v1->st[ j ] ); + for ( k = 0; k < MAX_LIGHTMAPS; k++ ) + mid.lightmap[ k ][ j ] = v1->lightmap[ k ][ j ] + dot * ( v2->lightmap[ k ][ j ] - v1->lightmap[ k ][ j ] ); + } + } + + /* normalize the averaged normal */ + VectorNormalize( mid.normal, mid.normal ); + + /* copy the midpoint to both windings */ + memcpy( &front->verts[ front->numVerts++ ], &mid, sizeof( radVert_t ) ); + memcpy( &back->verts[ back->numVerts++ ], &mid, sizeof( radVert_t ) ); + } + + /* error check */ + if ( front->numVerts > maxPoints || front->numVerts > maxPoints ) { + Error( "RadClipWindingEpsilon: points exceeded estimate" ); + } + if ( front->numVerts > MAX_POINTS_ON_WINDING || front->numVerts > MAX_POINTS_ON_WINDING ) { + Error( "RadClipWindingEpsilon: MAX_POINTS_ON_WINDING" ); + } +} + + + + + +/* + RadSampleImage() + samples a texture image for a given color + returns qfalse if pixels are bad + */ + +qboolean RadSampleImage( byte *pixels, int width, int height, float st[ 2 ], float color[ 4 ] ){ + float sto[ 2 ]; + int x, y; + + + /* clear color first */ + color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 255; + + /* dummy check */ + if ( pixels == NULL || width < 1 || height < 1 ) { + return qfalse; + } + + /* bias st */ + sto[ 0 ] = st[ 0 ]; + while ( sto[ 0 ] < 0.0f ) + sto[ 0 ] += 1.0f; + sto[ 1 ] = st[ 1 ]; + while ( sto[ 1 ] < 0.0f ) + sto[ 1 ] += 1.0f; + + /* get offsets */ + x = ( (float) width * sto[ 0 ] ) + 0.5f; + x %= width; + y = ( (float) height * sto[ 1 ] ) + 0.5f; + y %= height; + + /* get pixel */ + pixels += ( y * width * 4 ) + ( x * 4 ); + VectorCopy( pixels, color ); + color[ 3 ] = pixels[ 3 ]; + + if ( texturesRGB ) { + color[0] = Image_LinearFloatFromsRGBFloat( color[0] * ( 1.0 / 255.0 ) ) * 255.0; + color[1] = Image_LinearFloatFromsRGBFloat( color[1] * ( 1.0 / 255.0 ) ) * 255.0; + color[2] = Image_LinearFloatFromsRGBFloat( color[2] * ( 1.0 / 255.0 ) ) * 255.0; + } + + return qtrue; +} + + + +/* + RadSample() + samples a fragment's lightmap or vertex color and returns an + average color and a color gradient for the sample + */ + +#define MAX_SAMPLES 150 +#define SAMPLE_GRANULARITY 6 + +static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si, radWinding_t *rw, vec3_t average, vec3_t gradient, int *style ){ + int i, j, k, l, v, x, y, samples; + vec3_t color, mins, maxs; + vec4_t textureColor; + float alpha, alphaI, bf; + vec3_t blend; + float st[ 2 ], lightmap[ 2 ], *radLuxel; + radVert_t *rv[ 3 ]; + + if (!bouncing) + Sys_Printf( "BUG: RadSample: !bouncing shouldn't happen\n" ); + + /* initial setup */ + ClearBounds( mins, maxs ); + VectorClear( average ); + VectorClear( gradient ); + alpha = 0; + + /* dummy check */ + if ( rw == NULL || rw->numVerts < 3 ) { + return; + } + + /* start sampling */ + samples = 0; + + /* sample vertex colors if no lightmap or this is the initial pass */ + if ( lm == NULL || lm->radLuxels[ lightmapNum ] == NULL || bouncing == qfalse ) { + for ( samples = 0; samples < rw->numVerts; samples++ ) + { + /* multiply by texture color */ + if ( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, rw->verts[ samples ].st, textureColor ) ) { + VectorCopy( si->averageColor, textureColor ); + textureColor[ 4 ] = 255.0f; + } + for ( i = 0; i < 3; i++ ) + color[ i ] = ( textureColor[ i ] / 255 ) * ( rw->verts[ samples ].color[ lightmapNum ][ i ] / 255.0f ); + + AddPointToBounds( color, mins, maxs ); + VectorAdd( average, color, average ); + + /* get alpha */ + alpha += ( textureColor[ 3 ] / 255.0f ) * ( rw->verts[ samples ].color[ lightmapNum ][ 3 ] / 255.0f ); + } + + /* set style */ + *style = ds->vertexStyles[ lightmapNum ]; + } + + /* sample lightmap */ + else + { + /* fracture the winding into a fan (including degenerate tris) */ + for ( v = 1; v < ( rw->numVerts - 1 ) && samples < MAX_SAMPLES; v++ ) + { + /* get a triangle */ + rv[ 0 ] = &rw->verts[ 0 ]; + rv[ 1 ] = &rw->verts[ v ]; + rv[ 2 ] = &rw->verts[ v + 1 ]; + + /* this code is embarassing (really should just rasterize the triangle) */ + for ( i = 1; i < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; i++ ) + { + for ( j = 1; j < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; j++ ) + { + for ( k = 1; k < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; k++ ) + { + /* create a blend vector (barycentric coordinates) */ + blend[ 0 ] = i; + blend[ 1 ] = j; + blend[ 2 ] = k; + bf = ( 1.0 / ( blend[ 0 ] + blend[ 1 ] + blend[ 2 ] ) ); + VectorScale( blend, bf, blend ); + + /* create a blended sample */ + st[ 0 ] = st[ 1 ] = 0.0f; + lightmap[ 0 ] = lightmap[ 1 ] = 0.0f; + alphaI = 0.0f; + for ( l = 0; l < 3; l++ ) + { + st[ 0 ] += ( rv[ l ]->st[ 0 ] * blend[ l ] ); + st[ 1 ] += ( rv[ l ]->st[ 1 ] * blend[ l ] ); + lightmap[ 0 ] += ( rv[ l ]->lightmap[ lightmapNum ][ 0 ] * blend[ l ] ); + lightmap[ 1 ] += ( rv[ l ]->lightmap[ lightmapNum ][ 1 ] * blend[ l ] ); + alphaI += ( rv[ l ]->color[ lightmapNum ][ 3 ] * blend[ l ] ); + } + + /* get lightmap xy coords */ + x = lightmap[ 0 ] / (float) superSample; + y = lightmap[ 1 ] / (float) superSample; + if ( x < 0 ) { + x = 0; + } + else if ( x >= lm->w ) { + x = lm->w - 1; + } + if ( y < 0 ) { + y = 0; + } + else if ( y >= lm->h ) { + y = lm->h - 1; + } + + /* get radiosity luxel */ + radLuxel = RAD_LUXEL( lightmapNum, x, y ); + + /* ignore unlit/unused luxels */ + if ( radLuxel[ 0 ] < 0.0f ) { + continue; + } + + /* inc samples */ + samples++; + + /* multiply by texture color */ + if ( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, st, textureColor ) ) { + VectorCopy( si->averageColor, textureColor ); + textureColor[ 4 ] = 255; + } + for ( i = 0; i < 3; i++ ) + color[ i ] = ( textureColor[ i ] / 255 ) * ( radLuxel[ i ] / 255 ); + + AddPointToBounds( color, mins, maxs ); + VectorAdd( average, color, average ); + + /* get alpha */ + alpha += ( textureColor[ 3 ] / 255 ) * ( alphaI / 255 ); + } + } + } + } + + /* set style */ + *style = ds->lightmapStyles[ lightmapNum ]; + } + + /* any samples? */ + if ( samples <= 0 ) { + return; + } + + /* average the color */ + VectorScale( average, ( 1.0 / samples ), average ); + + /* create the color gradient */ + //% VectorSubtract( maxs, mins, delta ); + + /* new: color gradient will always be 0-1.0, expressed as the range of light relative to overall light */ + //% gradient[ 0 ] = maxs[ 0 ] > 0.0f ? (maxs[ 0 ] - mins[ 0 ]) / maxs[ 0 ] : 0.0f; + //% gradient[ 1 ] = maxs[ 1 ] > 0.0f ? (maxs[ 1 ] - mins[ 1 ]) / maxs[ 1 ] : 0.0f; + //% gradient[ 2 ] = maxs[ 2 ] > 0.0f ? (maxs[ 2 ] - mins[ 2 ]) / maxs[ 2 ] : 0.0f; + + /* newer: another contrast function */ + for ( i = 0; i < 3; i++ ) + gradient[ i ] = ( maxs[ i ] - mins[ i ] ) * maxs[ i ]; +} + + + +/* + RadSubdivideDiffuseLight() + subdivides a radiosity winding until it is smaller than subdivide, then generates an area light + */ + +#define RADIOSITY_MAX_GRADIENT 0.75f //% 0.25f +#define RADIOSITY_VALUE 500.0f +#define RADIOSITY_MIN 0.0001f +#define RADIOSITY_CLIP_EPSILON 0.125f + +static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si, + float scale, float subdivide, qboolean original, radWinding_t *rw, clipWork_t *cw ){ + int i, style = 0; + float dist, area, value; + vec3_t mins, maxs, normal, d1, d2, cross, color, gradient; + light_t *light, *splash; + winding_t *w; + + + /* dummy check */ + if ( rw == NULL || rw->numVerts < 3 ) { + return; + } + + /* get bounds for winding */ + ClearBounds( mins, maxs ); + for ( i = 0; i < rw->numVerts; i++ ) + AddPointToBounds( rw->verts[ i ].xyz, mins, maxs ); + + /* subdivide if necessary */ + for ( i = 0; i < 3; i++ ) + { + if ( maxs[ i ] - mins[ i ] > subdivide ) { + radWinding_t front, back; + + + /* make axial plane */ + VectorClear( normal ); + normal[ i ] = 1; + dist = ( maxs[ i ] + mins[ i ] ) * 0.5f; + + /* clip the winding */ + RadClipWindingEpsilon( rw, normal, dist, RADIOSITY_CLIP_EPSILON, &front, &back, cw ); + + /* recurse */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &front, cw ); + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &back, cw ); + return; + } + } + + /* check area */ + area = 0.0f; + for ( i = 2; i < rw->numVerts; i++ ) + { + VectorSubtract( rw->verts[ i - 1 ].xyz, rw->verts[ 0 ].xyz, d1 ); + VectorSubtract( rw->verts[ i ].xyz, rw->verts[ 0 ].xyz, d2 ); + CrossProduct( d1, d2, cross ); + area += 0.5f * VectorLength( cross ); + } + if ( area < 1.0f || area > 20000000.0f ) { + return; + } + + /* more subdivision may be necessary */ + if ( bouncing ) { + /* get color sample for the surface fragment */ + RadSample( lightmapNum, ds, lm, si, rw, color, gradient, &style ); + + /* if color gradient is too high, subdivide again */ + if ( subdivide > minDiffuseSubdivide && + ( gradient[ 0 ] > RADIOSITY_MAX_GRADIENT || gradient[ 1 ] > RADIOSITY_MAX_GRADIENT || gradient[ 2 ] > RADIOSITY_MAX_GRADIENT ) ) { + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, ( subdivide / 2.0f ), qfalse, rw, cw ); + return; + } + } + + /* create a regular winding and an average normal */ + w = AllocWinding( rw->numVerts ); + w->numpoints = rw->numVerts; + VectorClear( normal ); + for ( i = 0; i < rw->numVerts; i++ ) + { + VectorCopy( rw->verts[ i ].xyz, w->p[ i ] ); + VectorAdd( normal, rw->verts[ i ].normal, normal ); + } + VectorScale( normal, ( 1.0f / rw->numVerts ), normal ); + if ( VectorNormalize( normal, normal ) == 0.0f ) { + return; + } + + /* early out? */ + if ( bouncing && VectorLength( color ) < RADIOSITY_MIN ) { + return; + } + + /* debug code */ + //% Sys_Printf( "Size: %d %d %d\n", (int) (maxs[ 0 ] - mins[ 0 ]), (int) (maxs[ 1 ] - mins[ 1 ]), (int) (maxs[ 2 ] - mins[ 2 ]) ); + //% Sys_Printf( "Grad: %f %f %f\n", gradient[ 0 ], gradient[ 1 ], gradient[ 2 ] ); + + /* increment counts */ + numDiffuseLights++; + switch ( ds->surfaceType ) + { + case MST_PLANAR: + numBrushDiffuseLights++; + break; + + case MST_TRIANGLE_SOUP: + numTriangleDiffuseLights++; + break; + + case MST_PATCH: + case MST_PATCHFIXED: + numPatchDiffuseLights++; + break; + } + + /* create a light */ + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + + /* attach it */ + ThreadLock(); + light->next = lights; + lights = light; + ThreadUnlock(); + + /* initialize the light */ + light->flags = LIGHT_AREA_DEFAULT; + light->type = EMIT_AREA; + light->si = si; + light->fade = 1.0f; + light->w = w; + + /* set falloff threshold */ + light->falloffTolerance = falloffTolerance; + + /* bouncing light? */ + if ( bouncing == qfalse ) { + /* This is weird. This actually handles surfacelight and not + * bounces. */ + + /* handle first-pass lights in normal q3a style */ + value = si->value; + light->photons = value * area * areaScale; + light->add = value * formFactorValueScale * areaScale; + VectorCopy( si->color, light->color ); + VectorScale( light->color, light->add, light->emitColor ); + light->style = noStyles ? LS_NORMAL : si->lightStyle; + if ( light->style < LS_NORMAL || light->style >= LS_NONE ) { + light->style = LS_NORMAL; + } + + /* set origin */ + VectorAdd( mins, maxs, light->origin ); + VectorScale( light->origin, 0.5f, light->origin ); + + /* nudge it off the plane a bit */ + VectorCopy( normal, light->normal ); + VectorMA( light->origin, 1.0f, light->normal, light->origin ); + light->dist = DotProduct( light->origin, normal ); + + /* optionally create a point splashsplash light for first pass */ + if ( original && si->backsplashFraction > 0 ) { + /* allocate a new point light */ + splash = safe_malloc( sizeof( *splash ) ); + memset( splash, 0, sizeof( *splash ) ); + splash->next = lights; + lights = splash; + + /* set it up */ + splash->flags = LIGHT_Q3A_DEFAULT; + splash->type = EMIT_POINT; + splash->photons = light->photons * si->backsplashFraction; + splash->fade = 1.0f; + splash->si = si; + VectorMA( light->origin, si->backsplashDistance, normal, splash->origin ); + VectorCopy( si->color, splash->color ); + splash->falloffTolerance = falloffTolerance; + splash->style = noStyles ? LS_NORMAL : light->style; + + /* add to counts */ + numPointLights++; + } + } + else + { + /* handle bounced light (radiosity) a little differently */ + value = RADIOSITY_VALUE * si->bounceScale; + light->photons = value * area * bounceScale; + light->add = value * formFactorValueScale * bounceScale; + VectorCopy( color, light->color ); + VectorScale( light->color, light->add, light->emitColor ); + light->style = noStyles ? LS_NORMAL : style; + if ( light->style < LS_NORMAL || light->style >= LS_NONE ) { + light->style = LS_NORMAL; + } + + /* set origin */ + WindingCenter( w, light->origin ); + + /* nudge it off the plane a bit */ + VectorCopy( normal, light->normal ); + VectorMA( light->origin, 1.0f, light->normal, light->origin ); + light->dist = DotProduct( light->origin, normal ); + } + + if (light->photons < 0 || light->add < 0 || light->color[0] < 0 || light->color[1] < 0 || light->color[2] < 0) + Sys_Printf( "BUG: RadSubdivideDiffuseLight created a darkbulb\n" ); + + /* emit light from both sides? */ + if ( si->compileFlags & C_FOG || si->twoSided ) { + light->flags |= LIGHT_TWOSIDED; + } + + //% Sys_Printf( "\nAL: C: (%6f, %6f, %6f) [%6f] N: (%6f, %6f, %6f) %s\n", + //% light->color[ 0 ], light->color[ 1 ], light->color[ 2 ], light->add, + //% light->normal[ 0 ], light->normal[ 1 ], light->normal[ 2 ], + //% light->si->shader ); +} + + + +/* + RadLightForTriangles() + creates unbounced diffuse lights for triangle soup (misc_models, etc) + */ + +void RadLightForTriangles( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw ){ + int i, j, k, v; + bspDrawSurface_t *ds; + float *radVertexLuxel; + radWinding_t rw; + + + /* get surface */ + ds = &bspDrawSurfaces[ num ]; + + /* each triangle is a potential emitter */ + rw.numVerts = 3; + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + /* copy each vert */ + for ( j = 0; j < 3; j++ ) + { + /* get vertex index and rad vertex luxel */ + v = ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ]; + + /* get most everything */ + memcpy( &rw.verts[ j ], &yDrawVerts[ v ], sizeof( bspDrawVert_t ) ); + + /* fix colors */ + for ( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + radVertexLuxel = RAD_VERTEX_LUXEL( k, ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ] ); + VectorCopy( radVertexLuxel, rw.verts[ j ].color[ k ] ); + rw.verts[ j ].color[ k ][ 3 ] = yDrawVerts[ v ].color[ k ][ 3 ]; + } + } + + /* subdivide into area lights */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw ); + } +} + + + +/* + RadLightForPatch() + creates unbounced diffuse lights for patches + */ + +#define PLANAR_EPSILON 0.1f + +void RadLightForPatch( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw, qboolean fixed ){ + int i, x, y, v, t, pw[ 5 ], r; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + bspDrawVert_t *bogus; + bspDrawVert_t *dv[ 4 ]; + mesh_t src, *subdivided, *mesh; + float *radVertexLuxel; + float dist; + vec4_t plane; + qboolean planar; + radWinding_t rw; + + + /* get surface */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* construct a bogus vert list with color index stuffed into color[ 0 ] */ + bogus = safe_malloc( ds->numVerts * sizeof( bspDrawVert_t ) ); + memcpy( bogus, &yDrawVerts[ ds->firstVert ], ds->numVerts * sizeof( bspDrawVert_t ) ); + for ( i = 0; i < ds->numVerts; i++ ) + bogus[ i ].color[ 0 ][ 0 ] = i; + + /* build a subdivided mesh identical to shadow facets for this patch */ + /* this MUST MATCH FacetsForPatch() identically! */ + if (fixed) + { + src.width = ds->patchWidth&0xffff; + src.height = ds->patchHeight&0xffff; + src.subdiv_x = ds->patchWidth>>16; + src.subdiv_y = ds->patchHeight>>16; + } + else + { + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.subdiv_x = -1; + src.subdiv_y = -1; + } + src.verts = bogus; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + PutMeshOnCurve( *subdivided ); + //% MakeMeshNormals( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + free( bogus ); + + /* FIXME: build interpolation table into color[ 1 ] */ + + /* fix up color indexes */ + for ( i = 0; i < ( mesh->width * mesh->height ); i++ ) + { + dv[ 0 ] = &mesh->verts[ i ]; + if ( dv[ 0 ]->color[ 0 ][ 0 ] >= ds->numVerts ) { + dv[ 0 ]->color[ 0 ][ 0 ] = ds->numVerts - 1; + } + } + + /* iterate through the mesh quads */ + for ( y = 0; y < ( mesh->height - 1 ); y++ ) + { + for ( x = 0; x < ( mesh->width - 1 ); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + ( y * mesh->width ); + pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); + pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); + pw[ 3 ] = x + 1 + ( y * mesh->width ); + pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( x + y ) & 1; + + /* get drawverts */ + dv[ 0 ] = &mesh->verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &mesh->verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &mesh->verts[ pw[ r + 2 ] ]; + dv[ 3 ] = &mesh->verts[ pw[ r + 3 ] ]; + + /* planar? */ + planar = PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ); + if ( planar ) { + dist = DotProduct( dv[ 1 ]->xyz, plane ) - plane[ 3 ]; + if ( fabs( dist ) > PLANAR_EPSILON ) { + planar = qfalse; + } + } + + /* generate a quad */ + if ( planar ) { + rw.numVerts = 4; + for ( v = 0; v < 4; v++ ) + { + /* get most everything */ + memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) ); + + /* fix colors */ + for ( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] ); + VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] ); + rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ]; + } + } + + /* subdivide into area lights */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw ); + } + + /* generate 2 tris */ + else + { + rw.numVerts = 3; + for ( t = 0; t < 2; t++ ) + { + for ( v = 0; v < 3 + t; v++ ) + { + /* get "other" triangle (stupid hacky logic, but whatevah) */ + if ( v == 1 && t == 1 ) { + v++; + } + + /* get most everything */ + memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) ); + + /* fix colors */ + for ( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] ); + VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] ); + rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ]; + } + } + + /* subdivide into area lights */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw ); + } + } + } + } + + /* free the mesh */ + FreeMesh( mesh ); +} + + + + +/* + RadLight() + creates unbounced diffuse lights for a given surface + */ + +void RadLight( int num ){ + int lightmapNum; + float scale, subdivide; + int contentFlags, surfaceFlags, compileFlags; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + rawLightmap_t *lm; + shaderInfo_t *si; + clipWork_t cw; + + + /* get drawsurface, lightmap, and shader info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + lm = info->lm; + si = info->si; + scale = si->bounceScale; + + /* find nodraw bit */ + contentFlags = surfaceFlags = compileFlags = 0; + ApplySurfaceParm( "nodraw", &contentFlags, &surfaceFlags, &compileFlags ); + + // jal : avoid bouncing on trans surfaces + ApplySurfaceParm( "trans", &contentFlags, &surfaceFlags, &compileFlags ); + + /* early outs? */ + if ( scale <= 0.0f || ( si->compileFlags & C_SKY ) || si->autosprite || + ( bspShaders[ ds->shaderNum ].contentFlags & contentFlags ) || ( bspShaders[ ds->shaderNum ].surfaceFlags & surfaceFlags ) || + ( si->compileFlags & compileFlags ) ) { + return; + } + + /* determine how much we need to chop up the surface */ + if ( si->lightSubdivide ) { + subdivide = si->lightSubdivide; + } + else{ + subdivide = diffuseSubdivide; + } + + /* inc counts */ + numDiffuseSurfaces++; + + /* iterate through styles (this could be more efficient, yes) */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* switch on type */ + if ( ds->lightmapStyles[ lightmapNum ] != LS_NONE && ds->lightmapStyles[ lightmapNum ] != LS_UNUSED ) { + switch ( ds->surfaceType ) + { + case MST_PLANAR: + case MST_TRIANGLE_SOUP: + RadLightForTriangles( num, lightmapNum, lm, si, scale, subdivide, &cw ); + break; + + case MST_PATCH: + case MST_PATCHFIXED: + RadLightForPatch( num, lightmapNum, lm, si, scale, subdivide, &cw, (ds->surfaceType==MST_PATCHFIXED)?qtrue:qfalse ); + break; + + default: + break; + } + } + } +} + + + +/* + RadCreateDiffuseLights() + creates lights for unbounced light on surfaces in the bsp + */ + +int iterations = 0; + +void RadCreateDiffuseLights( void ){ + /* startup */ + Sys_FPrintf( SYS_VRB, "--- RadCreateDiffuseLights ---\n" ); + numDiffuseSurfaces = 0; + numDiffuseLights = 0; + numBrushDiffuseLights = 0; + numTriangleDiffuseLights = 0; + numPatchDiffuseLights = 0; + numAreaLights = 0; + + /* hit every surface (threaded) */ + RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, RadLight ); + + /* dump the lights generated to a file */ + if ( dump ) { + char dumpName[ 1024 ], ext[ 64 ]; + FILE *file; + light_t *light; + + strcpy( dumpName, source ); + StripExtension( dumpName ); + sprintf( ext, "_bounce_%03d.map", iterations ); + strcat( dumpName, ext ); + file = fopen( dumpName, "wb" ); + Sys_Printf( "Writing %s...\n", dumpName ); + if ( file ) { + for ( light = lights; light; light = light->next ) + { + fprintf( file, + "{\n" + "\"classname\" \"light\"\n" + "\"light\" \"%d\"\n" + "\"origin\" \"%.0f %.0f %.0f\"\n" + "\"_color\" \"%.3f %.3f %.3f\"\n" + "}\n", + + (int) light->add, + + light->origin[ 0 ], + light->origin[ 1 ], + light->origin[ 2 ], + + light->color[ 0 ], + light->color[ 1 ], + light->color[ 2 ] ); + } + fclose( file ); + } + } + + /* increment */ + iterations++; + + /* print counts */ + Sys_Printf( "%8d diffuse surfaces\n", numDiffuseSurfaces ); + Sys_FPrintf( SYS_VRB, "%8d total diffuse lights\n", numDiffuseLights ); + Sys_FPrintf( SYS_VRB, "%8d brush diffuse lights\n", numBrushDiffuseLights ); + Sys_FPrintf( SYS_VRB, "%8d patch diffuse lights\n", numPatchDiffuseLights ); + Sys_FPrintf( SYS_VRB, "%8d triangle diffuse lights\n", numTriangleDiffuseLights ); +} diff --git a/tools/vmap/light_shadows.c b/tools/vmap/light_shadows.c new file mode 100644 index 0000000..50a0d62 --- /dev/null +++ b/tools/vmap/light_shadows.c @@ -0,0 +1,128 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define LIGHT_SHADOWS_C + +#include "light.h" +#include "inout.h" + + + +/* ------------------------------------------------------------------------------- + + ydnar: this code deals with shadow volume bsps + + ------------------------------------------------------------------------------- */ + +typedef struct shadowNode_s +{ + vec4_t plane; + int children[ 2 ]; +} +shadowNode_t; + +int numShadowNodes; +shadowNode_t *shadowNodes; + + + +/* + AddShadow() + adds a shadow, returning the index into the shadow list + */ + + + +/* + MakeShadowFromPoints() + creates a shadow volume from 4 points (the first being the light origin) + */ + + + +/* + SetupShadows() + sets up the shadow volumes for all lights in the world + */ + +void SetupShadows( void ){ + int i, j, s; + light_t *light; + dleaf_t *leaf; + dsurface_t *ds; + surfaceInfo_t *info; + shaderInfo_t *si; + byte *tested; + + + /* early out for weird cases where there are no lights */ + if ( lights == NULL ) { + return; + } + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupShadows ---\n" ); + + /* allocate a surface test list */ + tested = safe_malloc( numDrawSurfaces / 8 + 1 ); + + /* walk the list of lights */ + for ( light = lights; light != NULL; light = light->next ) + { + /* do some early out testing */ + if ( light->cluster < 0 ) { + continue; + } + + /* clear surfacetest list */ + memset( tested, 0, numDrawSurfaces / 8 + 1 ); + + /* walk the bsp leaves */ + for ( i = 0, leaf = dleafs; i < numleafs; i++, leaf++ ) + { + /* in pvs? */ + if ( ClusterVisible( light->cluster, leaf->cluster ) == qfalse ) { + continue; + } + + /* walk the surface list for this leaf */ + for ( j = 0; j < leaf->numLeafSurfaces; j++ ) + { + /* don't filter a surface more than once */ + s = dleafsurfaces[ leaf->firstLeafSurface + j ]; + if ( tested[ s >> 3 ] & ( 1 << ( s & 7 ) ) ) { + continue; + } + tested[ s >> 3 ] |= ( 1 << ( s & 7 ) ); + + /* get surface and info */ + ds = &drawSurfaces[ s ]; + info = &surfaceInfos[ s ]; + si = info->si; + + /* don't create shadow volumes from translucent surfaces */ + if ( si->contents & CONTENTS_TRANSLUCENT ) { + continue; + } + } + } + } +} diff --git a/tools/vmap/light_trace.c b/tools/vmap/light_trace.c new file mode 100644 index 0000000..9adfdd6 --- /dev/null +++ b/tools/vmap/light_trace.c @@ -0,0 +1,1806 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_TRACE_C + + + +/* dependencies */ +#include "vmap.h" + + +/* dependencies */ +#include "vmap.h" + + + +#define Vector2Copy( a, b ) ( ( b )[ 0 ] = ( a )[ 0 ], ( b )[ 1 ] = ( a )[ 1 ] ) +#define Vector4Copy( a, b ) ( ( b )[ 0 ] = ( a )[ 0 ], ( b )[ 1 ] = ( a )[ 1 ], ( b )[ 2 ] = ( a )[ 2 ], ( b )[ 3 ] = ( a )[ 3 ] ) + +#define MAX_NODE_ITEMS 5 +#define MAX_NODE_TRIANGLES 5 +#define MAX_TRACE_DEPTH 32 +#define MIN_NODE_SIZE 32.0f + +#define GROW_TRACE_INFOS 32768 //% 4096 +#define GROW_TRACE_WINDINGS 65536 //% 32768 +#define GROW_TRACE_TRIANGLES 131072 //% 32768 +#define GROW_TRACE_NODES 16384 //% 16384 +#define GROW_NODE_ITEMS 16 //% 256 + +// vortex: increased from 12 to 24 for ability co compile some insane maps with large curve count +#define MAX_TW_VERTS 24 + +#define TRACE_ON_EPSILON 0.1f + +#define TRACE_LEAF -1 +#define TRACE_LEAF_SOLID -2 + +typedef struct traceVert_s +{ + vec3_t xyz; + float st[ 2 ]; +} +traceVert_t; + +typedef struct traceInfo_s +{ + shaderInfo_t *si; + int surfaceNum, castShadows, skipGrid; +} +traceInfo_t; + +typedef struct traceWinding_s +{ + vec4_t plane; + int infoNum, numVerts; + traceVert_t v[ MAX_TW_VERTS ]; +} +traceWinding_t; + +typedef struct traceTriangle_s +{ + vec3_t edge1, edge2; + int infoNum; + traceVert_t v[ 3 ]; +} +traceTriangle_t; + +typedef struct traceNode_s +{ + int type; + vec4_t plane; + vec3_t mins, maxs; + int children[ 2 ]; + int numItems, maxItems; + int *items; +} +traceNode_t; + + +int noDrawContentFlags, noDrawSurfaceFlags, noDrawCompileFlags; + +int numTraceInfos = 0, maxTraceInfos = 0, firstTraceInfo = 0; +traceInfo_t *traceInfos = NULL; + +int numTraceWindings = 0, maxTraceWindings = 0, deadWinding = -1; +traceWinding_t *traceWindings = NULL; + +int numTraceTriangles = 0, maxTraceTriangles = 0, deadTriangle = -1; +traceTriangle_t *traceTriangles = NULL; + +int headNodeNum = 0, skyboxNodeNum = 0, maxTraceDepth = 0, numTraceLeafNodes = 0; +int numTraceNodes = 0, maxTraceNodes = 0; +traceNode_t *traceNodes = NULL; + + + +/* ------------------------------------------------------------------------------- + + allocation and list management + + ------------------------------------------------------------------------------- */ + +/* + AddTraceInfo() - ydnar + adds a trace info structure to the pool + */ + +static int AddTraceInfo( traceInfo_t *ti ){ + int num; + void *temp; + + + /* find an existing info */ + for ( num = firstTraceInfo; num < numTraceInfos; num++ ) + { + if ( traceInfos[ num ].si == ti->si && + traceInfos[ num ].surfaceNum == ti->surfaceNum && + traceInfos[ num ].castShadows == ti->castShadows && + traceInfos[ num ].skipGrid == ti->skipGrid ) { + return num; + } + } + + /* enough space? */ + if ( numTraceInfos >= maxTraceInfos ) { + /* allocate more room */ + maxTraceInfos += GROW_TRACE_INFOS; + temp = safe_malloc( maxTraceInfos * sizeof( *traceInfos ) ); + if ( traceInfos != NULL ) { + memcpy( temp, traceInfos, numTraceInfos * sizeof( *traceInfos ) ); + free( traceInfos ); + } + traceInfos = (traceInfo_t*) temp; + } + + /* add the info */ + memcpy( &traceInfos[ num ], ti, sizeof( *traceInfos ) ); + if ( num == numTraceInfos ) { + numTraceInfos++; + } + + /* return the ti number */ + return num; +} + + + +/* + AllocTraceNode() - ydnar + allocates a new trace node + */ + +static int AllocTraceNode( void ){ + traceNode_t *temp; + + + /* enough space? */ + if ( numTraceNodes >= maxTraceNodes ) { + /* reallocate more room */ + maxTraceNodes += GROW_TRACE_NODES; + temp = safe_malloc( maxTraceNodes * sizeof( traceNode_t ) ); + if ( traceNodes != NULL ) { + memcpy( temp, traceNodes, numTraceNodes * sizeof( traceNode_t ) ); + free( traceNodes ); + } + traceNodes = temp; + } + + /* add the node */ + memset( &traceNodes[ numTraceNodes ], 0, sizeof( traceNode_t ) ); + traceNodes[ numTraceNodes ].type = TRACE_LEAF; + ClearBounds( traceNodes[ numTraceNodes ].mins, traceNodes[ numTraceNodes ].maxs ); + numTraceNodes++; + + /* return the count */ + return ( numTraceNodes - 1 ); +} + + + +/* + AddTraceWinding() - ydnar + adds a winding to the raytracing pool + */ + +static int AddTraceWinding( traceWinding_t *tw ){ + int num; + void *temp; + + + /* check for a dead winding */ + if ( deadWinding >= 0 && deadWinding < numTraceWindings ) { + num = deadWinding; + } + else + { + /* put winding at the end of the list */ + num = numTraceWindings; + + /* enough space? */ + if ( numTraceWindings >= maxTraceWindings ) { + /* allocate more room */ + maxTraceWindings += GROW_TRACE_WINDINGS; + temp = safe_malloc( maxTraceWindings * sizeof( *traceWindings ) ); + if ( traceWindings != NULL ) { + memcpy( temp, traceWindings, numTraceWindings * sizeof( *traceWindings ) ); + free( traceWindings ); + } + traceWindings = (traceWinding_t*) temp; + } + } + + /* add the winding */ + memcpy( &traceWindings[ num ], tw, sizeof( *traceWindings ) ); + if ( num == numTraceWindings ) { + numTraceWindings++; + } + deadWinding = -1; + + /* return the winding number */ + return num; +} + + + +/* + AddTraceTriangle() - ydnar + adds a triangle to the raytracing pool + */ + +static int AddTraceTriangle( traceTriangle_t *tt ){ + int num; + void *temp; + + + /* check for a dead triangle */ + if ( deadTriangle >= 0 && deadTriangle < numTraceTriangles ) { + num = deadTriangle; + } + else + { + /* put triangle at the end of the list */ + num = numTraceTriangles; + + /* enough space? */ + if ( numTraceTriangles >= maxTraceTriangles ) { + /* allocate more room */ + maxTraceTriangles += GROW_TRACE_TRIANGLES; + temp = safe_malloc( maxTraceTriangles * sizeof( *traceTriangles ) ); + if ( traceTriangles != NULL ) { + memcpy( temp, traceTriangles, numTraceTriangles * sizeof( *traceTriangles ) ); + free( traceTriangles ); + } + traceTriangles = (traceTriangle_t*) temp; + } + } + + /* find vectors for two edges sharing the first vert */ + VectorSubtract( tt->v[ 1 ].xyz, tt->v[ 0 ].xyz, tt->edge1 ); + VectorSubtract( tt->v[ 2 ].xyz, tt->v[ 0 ].xyz, tt->edge2 ); + + /* add the triangle */ + memcpy( &traceTriangles[ num ], tt, sizeof( *traceTriangles ) ); + if ( num == numTraceTriangles ) { + numTraceTriangles++; + } + deadTriangle = -1; + + /* return the triangle number */ + return num; +} + + + +/* + AddItemToTraceNode() - ydnar + adds an item reference (winding or triangle) to a trace node + */ + +static int AddItemToTraceNode( traceNode_t *node, int num ){ + void *temp; + + + /* dummy check */ + if ( num < 0 ) { + return -1; + } + + /* enough space? */ + if ( node->numItems >= node->maxItems ) { + /* allocate more room */ + if ( node == traceNodes ) { + node->maxItems *= 2; + } + else{ + node->maxItems += GROW_NODE_ITEMS; + } + if ( node->maxItems <= 0 ) { + node->maxItems = GROW_NODE_ITEMS; + } + temp = safe_malloc( node->maxItems * sizeof( *node->items ) ); + if ( node->items != NULL ) { + memcpy( temp, node->items, node->numItems * sizeof( *node->items ) ); + free( node->items ); + } + node->items = (int*) temp; + } + + /* add the poly */ + node->items[ node->numItems ] = num; + node->numItems++; + + /* return the count */ + return ( node->numItems - 1 ); +} + + + + +/* ------------------------------------------------------------------------------- + + trace node setup + + ------------------------------------------------------------------------------- */ + +/* + SetupTraceNodes_r() - ydnar + recursively create the initial trace node structure from the bsp tree + */ + +static int SetupTraceNodes_r( int bspNodeNum ){ + int i, nodeNum, bspLeafNum, newNode; + bspPlane_t *plane; + bspNode_t *bspNode; + + + /* get bsp node and plane */ + bspNode = &bspNodes[ bspNodeNum ]; + plane = &bspPlanes[ bspNode->planeNum ]; + + /* allocate a new trace node */ + nodeNum = AllocTraceNode(); + + /* setup trace node */ + traceNodes[ nodeNum ].type = PlaneTypeForNormal( plane->normal ); + VectorCopy( plane->normal, traceNodes[ nodeNum ].plane ); + traceNodes[ nodeNum ].plane[ 3 ] = plane->dist; + + /* setup children */ + for ( i = 0; i < 2; i++ ) + { + /* leafnode */ + if ( bspNode->children[ i ] < 0 ) { + bspLeafNum = -bspNode->children[ i ] - 1; + + /* new code */ + newNode = AllocTraceNode(); + traceNodes[ nodeNum ].children[ i ] = newNode; + /* have to do this separately, as gcc first executes LHS, then RHS, and if a realloc took place, this fails */ + + if ( bspLeafs[ bspLeafNum ].cluster == -1 ) { + traceNodes[ traceNodes[ nodeNum ].children[ i ] ].type = TRACE_LEAF_SOLID; + } + } + + /* normal node */ + else + { + newNode = SetupTraceNodes_r( bspNode->children[ i ] ); + traceNodes[ nodeNum ].children[ i ] = newNode; + } + + if ( traceNodes[ nodeNum ].children[ i ] == 0 ) { + Error( "Invalid tracenode allocated" ); + } + } + + /* return node number */ + return nodeNum; +} + + + +/* + ClipTraceWinding() - ydnar + clips a trace winding against a plane into one or two parts + */ + +#define TW_ON_EPSILON 0.25f + +void ClipTraceWinding( traceWinding_t *tw, vec4_t plane, traceWinding_t *front, traceWinding_t *back ){ + int i, j, k; + int sides[ MAX_TW_VERTS ], counts[ 3 ] = { 0, 0, 0 }; + float dists[ MAX_TW_VERTS ]; + float frac; + traceVert_t *a, *b, mid; + + + /* clear front and back */ + front->numVerts = 0; + back->numVerts = 0; + + /* classify points */ + for ( i = 0; i < tw->numVerts; i++ ) + { + dists[ i ] = DotProduct( tw->v[ i ].xyz, plane ) - plane[ 3 ]; + if ( dists[ i ] < -TW_ON_EPSILON ) { + sides[ i ] = SIDE_BACK; + } + else if ( dists[ i ] > TW_ON_EPSILON ) { + sides[ i ] = SIDE_FRONT; + } + else{ + sides[ i ] = SIDE_ON; + } + counts[ sides[ i ] ]++; + } + + /* entirely on front? */ + if ( counts[ SIDE_BACK ] == 0 ) { + memcpy( front, tw, sizeof( *front ) ); + } + + /* entirely on back? */ + else if ( counts[ SIDE_FRONT ] == 0 ) { + memcpy( back, tw, sizeof( *back ) ); + } + + /* straddles the plane */ + else + { + /* setup front and back */ + memcpy( front, tw, sizeof( *front ) ); + front->numVerts = 0; + memcpy( back, tw, sizeof( *back ) ); + back->numVerts = 0; + + /* split the winding */ + for ( i = 0; i < tw->numVerts; i++ ) + { + /* radix */ + j = ( i + 1 ) % tw->numVerts; + + /* get verts */ + a = &tw->v[ i ]; + b = &tw->v[ j ]; + + /* handle points on the splitting plane */ + switch ( sides[ i ] ) + { + case SIDE_FRONT: + if ( front->numVerts >= MAX_TW_VERTS ) { + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + } + front->v[ front->numVerts++ ] = *a; + break; + + case SIDE_BACK: + if ( back->numVerts >= MAX_TW_VERTS ) { + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + } + back->v[ back->numVerts++ ] = *a; + break; + + case SIDE_ON: + if ( front->numVerts >= MAX_TW_VERTS || back->numVerts >= MAX_TW_VERTS ) { + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + } + front->v[ front->numVerts++ ] = *a; + back->v[ back->numVerts++ ] = *a; + continue; + } + + /* check next point to see if we need to split the edge */ + if ( sides[ j ] == SIDE_ON || sides[ j ] == sides[ i ] ) { + continue; + } + + /* check limit */ + if ( front->numVerts >= MAX_TW_VERTS || back->numVerts >= MAX_TW_VERTS ) { + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + } + + /* generate a split point */ + frac = dists[ i ] / ( dists[ i ] - dists[ j ] ); + for ( k = 0; k < 3; k++ ) + { + /* minimize fp precision errors */ + if ( plane[ k ] == 1.0f ) { + mid.xyz[ k ] = plane[ 3 ]; + } + else if ( plane[ k ] == -1.0f ) { + mid.xyz[ k ] = -plane[ 3 ]; + } + else{ + mid.xyz[ k ] = a->xyz[ k ] + frac * ( b->xyz[ k ] - a->xyz[ k ] ); + } + } + /* set texture coordinates */ + mid.st[ 0 ] = a->st[ 0 ] + frac * ( b->st[ 0 ] - a->st[ 0 ] ); + mid.st[ 1 ] = a->st[ 1 ] + frac * ( b->st[ 1 ] - a->st[ 1 ] ); + + /* copy midpoint to front and back polygons */ + front->v[ front->numVerts++ ] = mid; + back->v[ back->numVerts++ ] = mid; + } + } +} + + + +/* + FilterTraceWindingIntoNodes_r() - ydnar + filters a trace winding into the raytracing tree + */ + +static void FilterTraceWindingIntoNodes_r( traceWinding_t *tw, int nodeNum ){ + int num; + vec4_t plane1, plane2, reverse; + traceNode_t *node; + traceWinding_t front, back; + + + /* don't filter if passed a bogus node (solid, etc) */ + if ( nodeNum < 0 || nodeNum >= numTraceNodes ) { + return; + } + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* is this a decision node? */ + if ( node->type >= 0 ) { + /* create winding plane if necessary, filtering out bogus windings as well */ + if ( nodeNum == headNodeNum ) { + if ( !PlaneFromPoints( tw->plane, tw->v[ 0 ].xyz, tw->v[ 1 ].xyz, tw->v[ 2 ].xyz ) ) { + return; + } + } + + /* validate the node */ + if ( node->children[ 0 ] == 0 || node->children[ 1 ] == 0 ) { + Error( "Invalid tracenode: %d", nodeNum ); + } + + /* get node plane */ + Vector4Copy( node->plane, plane1 ); + + /* get winding plane */ + Vector4Copy( tw->plane, plane2 ); + + /* invert surface plane */ + VectorSubtract( vec3_origin, plane2, reverse ); + reverse[ 3 ] = -plane2[ 3 ]; + + /* front only */ + if ( DotProduct( plane1, plane2 ) > 0.999f && fabs( plane1[ 3 ] - plane2[ 3 ] ) < 0.001f ) { + FilterTraceWindingIntoNodes_r( tw, node->children[ 0 ] ); + return; + } + + /* back only */ + if ( DotProduct( plane1, reverse ) > 0.999f && fabs( plane1[ 3 ] - reverse[ 3 ] ) < 0.001f ) { + FilterTraceWindingIntoNodes_r( tw, node->children[ 1 ] ); + return; + } + + /* clip the winding by node plane */ + ClipTraceWinding( tw, plane1, &front, &back ); + + /* filter by node plane */ + if ( front.numVerts >= 3 ) { + FilterTraceWindingIntoNodes_r( &front, node->children[ 0 ] ); + } + if ( back.numVerts >= 3 ) { + FilterTraceWindingIntoNodes_r( &back, node->children[ 1 ] ); + } + + /* return to caller */ + return; + } + + /* add winding to leaf node */ + num = AddTraceWinding( tw ); + AddItemToTraceNode( node, num ); +} + + + +/* + SubdivideTraceNode_r() - ydnar + recursively subdivides a tracing node until it meets certain size and complexity criteria + */ + +static void SubdivideTraceNode_r( int nodeNum, int depth ){ + int i, j, count, num, frontNum, backNum, type; + vec3_t size; + float dist; + double average[ 3 ]; + traceNode_t *node, *frontNode, *backNode; + traceWinding_t *tw, front, back; + + + /* dummy check */ + if ( nodeNum < 0 || nodeNum >= numTraceNodes ) { + return; + } + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* runaway recursion check */ + if ( depth >= MAX_TRACE_DEPTH ) { + //% Sys_Printf( "Depth: (%d items)\n", node->numItems ); + numTraceLeafNodes++; + return; + } + depth++; + + /* is this a decision node? */ + if ( node->type >= 0 ) { + /* subdivide children */ + frontNum = node->children[ 0 ]; + backNum = node->children[ 1 ]; + SubdivideTraceNode_r( frontNum, depth ); + SubdivideTraceNode_r( backNum, depth ); + return; + } + + /* bound the node */ + ClearBounds( node->mins, node->maxs ); + VectorClear( average ); + count = 0; + for ( i = 0; i < node->numItems; i++ ) + { + /* get winding */ + tw = &traceWindings[ node->items[ i ] ]; + + /* walk its verts */ + for ( j = 0; j < tw->numVerts; j++ ) + { + AddPointToBounds( tw->v[ j ].xyz, node->mins, node->maxs ); + average[ 0 ] += tw->v[ j ].xyz[ 0 ]; + average[ 1 ] += tw->v[ j ].xyz[ 1 ]; + average[ 2 ] += tw->v[ j ].xyz[ 2 ]; + count++; + } + } + + /* check triangle limit */ + //% if( node->numItems <= MAX_NODE_ITEMS ) + if ( ( count - ( node->numItems * 2 ) ) < MAX_NODE_TRIANGLES ) { + //% Sys_Printf( "Limit: (%d triangles)\n", (count - (node->numItems * 2)) ); + numTraceLeafNodes++; + return; + } + + /* the largest dimension of the bounding box will be the split axis */ + VectorSubtract( node->maxs, node->mins, size ); + if ( size[ 0 ] >= size[ 1 ] && size[ 0 ] >= size[ 2 ] ) { + type = PLANE_X; + } + else if ( size[ 1 ] >= size[ 0 ] && size[ 1 ] >= size[ 2 ] ) { + type = PLANE_Y; + } + else{ + type = PLANE_Z; + } + + /* don't split small nodes */ + if ( size[ type ] <= MIN_NODE_SIZE ) { + //% Sys_Printf( "Limit: %f %f %f (%d items)\n", size[ 0 ], size[ 1 ], size[ 2 ], node->numItems ); + numTraceLeafNodes++; + return; + } + + /* set max trace depth */ + if ( depth > maxTraceDepth ) { + maxTraceDepth = depth; + } + + /* snap the average */ + dist = floor( average[ type ] / count ); + + /* dummy check it */ + if ( dist <= node->mins[ type ] || dist >= node->maxs[ type ] ) { + dist = floor( 0.5f * ( node->mins[ type ] + node->maxs[ type ] ) ); + } + + /* allocate child nodes */ + frontNum = AllocTraceNode(); + backNum = AllocTraceNode(); + + /* reset pointers */ + node = &traceNodes[ nodeNum ]; + frontNode = &traceNodes[ frontNum ]; + backNode = &traceNodes[ backNum ]; + + /* attach children */ + node->type = type; + node->plane[ type ] = 1.0f; + node->plane[ 3 ] = dist; + node->children[ 0 ] = frontNum; + node->children[ 1 ] = backNum; + + /* setup front node */ + frontNode->maxItems = ( node->maxItems >> 1 ); + frontNode->items = safe_malloc( frontNode->maxItems * sizeof( *frontNode->items ) ); + + /* setup back node */ + backNode->maxItems = ( node->maxItems >> 1 ); + backNode->items = safe_malloc( backNode->maxItems * sizeof( *backNode->items ) ); + + /* filter windings into child nodes */ + for ( i = 0; i < node->numItems; i++ ) + { + /* get winding */ + tw = &traceWindings[ node->items[ i ] ]; + + /* clip the winding by the new split plane */ + ClipTraceWinding( tw, node->plane, &front, &back ); + + /* kill the existing winding */ + if ( front.numVerts >= 3 || back.numVerts >= 3 ) { + deadWinding = node->items[ i ]; + } + + /* add front winding */ + if ( front.numVerts >= 3 ) { + num = AddTraceWinding( &front ); + AddItemToTraceNode( frontNode, num ); + } + + /* add back winding */ + if ( back.numVerts >= 3 ) { + num = AddTraceWinding( &back ); + AddItemToTraceNode( backNode, num ); + } + } + + /* free original node winding list */ + node->numItems = 0; + node->maxItems = 0; + free( node->items ); + node->items = NULL; + + /* check children */ + if ( frontNode->numItems <= 0 ) { + frontNode->maxItems = 0; + free( frontNode->items ); + frontNode->items = NULL; + } + + if ( backNode->numItems <= 0 ) { + backNode->maxItems = 0; + free( backNode->items ); + backNode->items = NULL; + } + + /* subdivide children */ + SubdivideTraceNode_r( frontNum, depth ); + SubdivideTraceNode_r( backNum, depth ); +} + + + +/* + TriangulateTraceNode_r() + optimizes the tracing data by changing trace windings into triangles + */ + +static int TriangulateTraceNode_r( int nodeNum ){ + int i, j, num, frontNum, backNum, numWindings, *windings; + traceNode_t *node; + traceWinding_t *tw; + traceTriangle_t tt; + + + /* dummy check */ + if ( nodeNum < 0 || nodeNum >= numTraceNodes ) { + return 0; + } + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* is this a decision node? */ + if ( node->type >= 0 ) { + /* triangulate children */ + frontNum = node->children[ 0 ]; + backNum = node->children[ 1 ]; + node->numItems = TriangulateTraceNode_r( frontNum ); + node->numItems += TriangulateTraceNode_r( backNum ); + return node->numItems; + } + + /* empty node? */ + if ( node->numItems == 0 ) { + node->maxItems = 0; + if ( node->items != NULL ) { + free( node->items ); + } + return node->numItems; + } + + /* store off winding data */ + numWindings = node->numItems; + windings = node->items; + + /* clear it */ + node->numItems = 0; + node->maxItems = numWindings * 2; + node->items = safe_malloc( node->maxItems * sizeof( tt ) ); + + /* walk winding list */ + for ( i = 0; i < numWindings; i++ ) + { + /* get winding */ + tw = &traceWindings[ windings[ i ] ]; + + /* initial setup */ + tt.infoNum = tw->infoNum; + tt.v[ 0 ] = tw->v[ 0 ]; + + /* walk vertex list */ + for ( j = 1; j + 1 < tw->numVerts; j++ ) + { + /* set verts */ + tt.v[ 1 ] = tw->v[ j ]; + tt.v[ 2 ] = tw->v[ j + 1 ]; + + /* find vectors for two edges sharing the first vert */ + VectorSubtract( tt.v[ 1 ].xyz, tt.v[ 0 ].xyz, tt.edge1 ); + VectorSubtract( tt.v[ 2 ].xyz, tt.v[ 0 ].xyz, tt.edge2 ); + + /* add it to the node */ + num = AddTraceTriangle( &tt ); + AddItemToTraceNode( node, num ); + } + } + + /* free windings */ + if ( windings != NULL ) { + free( windings ); + } + + /* return item count */ + return node->numItems; +} + + + +/* ------------------------------------------------------------------------------- + + shadow casting item setup (triangles, patches, entities) + + ------------------------------------------------------------------------------- */ + +/* + PopulateWithBSPModel() - ydnar + filters a bsp model's surfaces into the raytracing tree + */ + +static void PopulateWithBSPModel( bspModel_t *model, m4x4_t transform ){ + int i, j, x, y, pw[ 5 ], r, nodeNum; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + bspDrawVert_t *verts; + int *indexes; + mesh_t srcMesh, *mesh, *subdivided; + traceInfo_t ti; + traceWinding_t tw; + + + /* dummy check */ + if ( model == NULL || transform == NULL ) { + return; + } + + /* walk the list of surfaces in this model and fill out the info structs */ + for ( i = 0; i < model->numBSPSurfaces; i++ ) + { + /* get surface and info */ + ds = &bspDrawSurfaces[ model->firstBSPSurface + i ]; + info = &surfaceInfos[ model->firstBSPSurface + i ]; + if ( info->si == NULL ) { + continue; + } + + /* no shadows */ + if ( !info->castShadows ) { + continue; + } + + /* patchshadows? */ + if ( (ds->surfaceType == MST_PATCH||ds->surfaceType == MST_PATCHFIXED) && patchShadows == qfalse ) { + continue; + } + + /* some surfaces in the bsp might have been tagged as nodraw, with a bogus shader */ + if ( ( bspShaders[ ds->shaderNum ].contentFlags & noDrawContentFlags ) || + ( bspShaders[ ds->shaderNum ].surfaceFlags & noDrawSurfaceFlags ) ) { + continue; + } + + /* translucent surfaces that are neither alphashadow or lightfilter don't cast shadows */ + if ( ( info->si->compileFlags & C_NODRAW ) ) { + continue; + } + if ( ( info->si->compileFlags & C_TRANSLUCENT ) && + !( info->si->compileFlags & C_ALPHASHADOW ) && + !( info->si->compileFlags & C_LIGHTFILTER ) ) { + continue; + } + + /* setup trace info */ + ti.si = info->si; + ti.castShadows = info->castShadows; + ti.surfaceNum = model->firstBSPBrush + i; + ti.skipGrid = ( ds->surfaceType == MST_PATCH || ds->surfaceType == MST_PATCHFIXED ); + + /* choose which node (normal or skybox) */ + if ( info->parentSurfaceNum >= 0 ) { + nodeNum = skyboxNodeNum; + + /* sky surfaces in portal skies are ignored */ + if ( info->si->compileFlags & C_SKY ) { + continue; + } + } + else{ + nodeNum = headNodeNum; + } + + /* setup trace winding */ + memset( &tw, 0, sizeof( tw ) ); + tw.infoNum = AddTraceInfo( &ti ); + tw.numVerts = 3; + + /* switch on type */ + switch ( ds->surfaceType ) + { + /* handle patches */ + case MST_PATCH: + case MST_PATCHFIXED: + /* subdivide the surface */ + if (ds->surfaceType == MST_PATCHFIXED) + { + srcMesh.width = ds->patchWidth&0xffff; + srcMesh.height = ds->patchHeight&0xffff; + srcMesh.subdiv_x = ds->patchWidth>>16; + srcMesh.subdiv_y = ds->patchHeight>>16; + } + else + { + srcMesh.width = ds->patchWidth; + srcMesh.height = ds->patchHeight; + srcMesh.subdiv_x = -1; + srcMesh.subdiv_y = -1; + } + srcMesh.verts = &bspDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( srcMesh, 8, 512 ); + subdivided = SubdivideMesh2( srcMesh, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* set verts */ + verts = mesh->verts; + + /* subdivide each quad to place the models */ + for ( y = 0; y < ( mesh->height - 1 ); y++ ) + { + for ( x = 0; x < ( mesh->width - 1 ); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + ( y * mesh->width ); + pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); + pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); + pw[ 3 ] = x + 1 + ( y * mesh->width ); + pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( x + y ) & 1; + + /* make first triangle */ + VectorCopy( verts[ pw[ r + 0 ] ].xyz, tw.v[ 0 ].xyz ); + Vector2Copy( verts[ pw[ r + 0 ] ].st, tw.v[ 0 ].st ); + VectorCopy( verts[ pw[ r + 1 ] ].xyz, tw.v[ 1 ].xyz ); + Vector2Copy( verts[ pw[ r + 1 ] ].st, tw.v[ 1 ].st ); + VectorCopy( verts[ pw[ r + 2 ] ].xyz, tw.v[ 2 ].xyz ); + Vector2Copy( verts[ pw[ r + 2 ] ].st, tw.v[ 2 ].st ); + m4x4_transform_point( transform, tw.v[ 0 ].xyz ); + m4x4_transform_point( transform, tw.v[ 1 ].xyz ); + m4x4_transform_point( transform, tw.v[ 2 ].xyz ); + FilterTraceWindingIntoNodes_r( &tw, nodeNum ); + + /* make second triangle */ + VectorCopy( verts[ pw[ r + 0 ] ].xyz, tw.v[ 0 ].xyz ); + Vector2Copy( verts[ pw[ r + 0 ] ].st, tw.v[ 0 ].st ); + VectorCopy( verts[ pw[ r + 2 ] ].xyz, tw.v[ 1 ].xyz ); + Vector2Copy( verts[ pw[ r + 2 ] ].st, tw.v[ 1 ].st ); + VectorCopy( verts[ pw[ r + 3 ] ].xyz, tw.v[ 2 ].xyz ); + Vector2Copy( verts[ pw[ r + 3 ] ].st, tw.v[ 2 ].st ); + m4x4_transform_point( transform, tw.v[ 0 ].xyz ); + m4x4_transform_point( transform, tw.v[ 1 ].xyz ); + m4x4_transform_point( transform, tw.v[ 2 ].xyz ); + FilterTraceWindingIntoNodes_r( &tw, nodeNum ); + } + } + + /* free the subdivided mesh */ + FreeMesh( mesh ); + break; + + /* handle triangle surfaces */ + case MST_TRIANGLE_SOUP: + case MST_PLANAR: + /* set verts and indexes */ + verts = &bspDrawVerts[ ds->firstVert ]; + indexes = &bspDrawIndexes[ ds->firstIndex ]; + + /* walk the triangle list */ + for ( j = 0; j < ds->numIndexes; j += 3 ) + { + VectorCopy( verts[ indexes[ j ] ].xyz, tw.v[ 0 ].xyz ); + Vector2Copy( verts[ indexes[ j ] ].st, tw.v[ 0 ].st ); + VectorCopy( verts[ indexes[ j + 1 ] ].xyz, tw.v[ 1 ].xyz ); + Vector2Copy( verts[ indexes[ j + 1 ] ].st, tw.v[ 1 ].st ); + VectorCopy( verts[ indexes[ j + 2 ] ].xyz, tw.v[ 2 ].xyz ); + Vector2Copy( verts[ indexes[ j + 2 ] ].st, tw.v[ 2 ].st ); + m4x4_transform_point( transform, tw.v[ 0 ].xyz ); + m4x4_transform_point( transform, tw.v[ 1 ].xyz ); + m4x4_transform_point( transform, tw.v[ 2 ].xyz ); + FilterTraceWindingIntoNodes_r( &tw, nodeNum ); + } + break; + + /* other surface types do not cast shadows */ + default: + break; + } + } +} + + + +/* + PopulateWithPicoModel() - ydnar + filters a picomodel's surfaces into the raytracing tree + */ + +static void PopulateWithPicoModel( int castShadows, picoModel_t *model, m4x4_t transform ){ + int i, j, k, numSurfaces, numIndexes; + picoSurface_t *surface; + picoShader_t *shader; + picoVec_t *xyz, *st; + picoIndex_t *indexes; + traceInfo_t ti; + traceWinding_t tw; + + + /* dummy check */ + if ( model == NULL || transform == NULL ) { + return; + } + + /* get info */ + numSurfaces = PicoGetModelNumSurfaces( model ); + + /* walk the list of surfaces in this model and fill out the info structs */ + for ( i = 0; i < numSurfaces; i++ ) + { + /* get surface */ + surface = PicoGetModelSurface( model, i ); + if ( surface == NULL ) { + continue; + } + + /* only handle triangle surfaces initially (fixme: support patches) */ + if ( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) { + continue; + } + + /* get shader (fixme: support shader remapping) */ + shader = PicoGetSurfaceShader( surface ); + if ( shader == NULL ) { + continue; + } + ti.si = ShaderInfoForShaderNull( PicoGetShaderName( shader ) ); + if ( ti.si == NULL ) { + continue; + } + + /* translucent surfaces that are neither alphashadow or lightfilter don't cast shadows */ + if ( ( ti.si->compileFlags & C_NODRAW ) ) { + continue; + } + if ( ( ti.si->compileFlags & C_TRANSLUCENT ) && + !( ti.si->compileFlags & C_ALPHASHADOW ) && + !( ti.si->compileFlags & C_LIGHTFILTER ) ) { + continue; + } + + /* setup trace info */ + ti.castShadows = castShadows; + ti.surfaceNum = -1; + ti.skipGrid = qtrue; // also ignore picomodels when skipping patches + + /* setup trace winding */ + memset( &tw, 0, sizeof( tw ) ); + tw.infoNum = AddTraceInfo( &ti ); + tw.numVerts = 3; + + /* get info */ + numIndexes = PicoGetSurfaceNumIndexes( surface ); + indexes = PicoGetSurfaceIndexes( surface, 0 ); + + /* walk the triangle list */ + for ( j = 0; j < numIndexes; j += 3, indexes += 3 ) + { + for ( k = 0; k < 3; k++ ) + { + xyz = PicoGetSurfaceXYZ( surface, indexes[ k ] ); + st = PicoGetSurfaceST( surface, 0, indexes[ k ] ); + VectorCopy( xyz, tw.v[ k ].xyz ); + Vector2Copy( st, tw.v[ k ].st ); + m4x4_transform_point( transform, tw.v[ k ].xyz ); + } + FilterTraceWindingIntoNodes_r( &tw, headNodeNum ); + } + } +} + + + +/* + PopulateTraceNodes() - ydnar + fills the raytracing tree with world and entity occluders + */ + +static void PopulateTraceNodes( void ){ + int i, m, frame, castShadows; + float temp; + entity_t *e; + const char *value; + picoModel_t *model; + vec3_t origin, scale, angles; + m4x4_t transform; + + + /* add worldspawn triangles */ + m4x4_identity( transform ); + PopulateWithBSPModel( &bspModels[ 0 ], transform ); + + /* walk each entity list */ + for ( i = 1; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + + /* get shadow flags */ + castShadows = ENTITY_CAST_SHADOWS; + GetEntityShadowFlags( e, NULL, &castShadows, NULL ); + + /* early out? */ + if ( !castShadows ) { + continue; + } + + /* get entity origin */ + GetVectorForKey( e, "origin", origin ); + + /* get scale */ + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = 1.0f; + temp = FloatForKey( e, "modelscale" ); + if ( temp != 0.0f ) { + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp; + } + value = ValueForKey( e, "modelscale_vec" ); + if ( value[ 0 ] != '\0' ) { + sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] ); + } + + /* get "angle" (yaw) or "angles" (pitch yaw roll) */ + angles[ 0 ] = angles[ 1 ] = angles[ 2 ] = 0.0f; + angles[ 2 ] = FloatForKey( e, "angle" ); + value = ValueForKey( e, "angles" ); + if ( value[ 0 ] != '\0' ) { + sscanf( value, "%f %f %f", &angles[ 1 ], &angles[ 2 ], &angles[ 0 ] ); + } + + /* set transform matrix (thanks spog) */ + m4x4_identity( transform ); + m4x4_pivoted_transform_by_vec3( transform, origin, angles, eXYZ, scale, vec3_origin ); + + /* hack: Stable-1_2 and trunk have differing row/column major matrix order + this transpose is necessary with Stable-1_2 + uncomment the following line with old m4x4_t (non 1.3/spog_branch) code */ + //% m4x4_transpose( transform ); + + /* get model */ + value = ValueForKey( e, "model" ); + + /* switch on model type */ + switch ( value[ 0 ] ) + { + /* no model */ + case '\0': + break; + + /* bsp model */ + case '*': + m = atoi( &value[ 1 ] ); + if ( m <= 0 || m >= numBSPModels ) { + continue; + } + PopulateWithBSPModel( &bspModels[ m ], transform ); + break; + + /* external model */ + default: + frame = 0; + if ( strcmp( "", ValueForKey( e, "_frame" ) ) ) { + frame = IntForKey( e, "_frame" ); + } + else if ( strcmp( "", ValueForKey( e, "frame" ) ) ) { + frame = IntForKey( e, "frame" ); + } + model = LoadModel( value, frame ); + if ( model == NULL ) { + continue; + } + PopulateWithPicoModel( castShadows, model, transform ); + continue; + } + + /* get model2 */ + value = ValueForKey( e, "model2" ); + + /* switch on model type */ + switch ( value[ 0 ] ) + { + /* no model */ + case '\0': + break; + + /* bsp model */ + case '*': + m = atoi( &value[ 1 ] ); + if ( m <= 0 || m >= numBSPModels ) { + continue; + } + PopulateWithBSPModel( &bspModels[ m ], transform ); + break; + + /* external model */ + default: + frame = IntForKey( e, "_frame2" ); + model = LoadModel( value, frame ); + if ( model == NULL ) { + continue; + } + PopulateWithPicoModel( castShadows, model, transform ); + continue; + } + } +} + + + + +/* ------------------------------------------------------------------------------- + + trace initialization + + ------------------------------------------------------------------------------- */ + +/* + SetupTraceNodes() - ydnar + creates a balanced bsp with axis-aligned splits for efficient raytracing + */ + +void SetupTraceNodes( void ){ + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupTraceNodes ---\n" ); + + /* find nodraw bit */ + noDrawContentFlags = noDrawSurfaceFlags = noDrawCompileFlags = 0; + ApplySurfaceParm( "nodraw", &noDrawContentFlags, &noDrawSurfaceFlags, &noDrawCompileFlags ); + + /* create the baseline raytracing tree from the bsp tree */ + headNodeNum = SetupTraceNodes_r( 0 ); + + /* create outside node for skybox surfaces */ + skyboxNodeNum = AllocTraceNode(); + + /* populate the tree with triangles from the world and shadow casting entities */ + PopulateTraceNodes(); + + /* create the raytracing bsp */ + if ( loMem == qfalse ) { + SubdivideTraceNode_r( headNodeNum, 0 ); + SubdivideTraceNode_r( skyboxNodeNum, 0 ); + } + + /* create triangles from the trace windings */ + TriangulateTraceNode_r( headNodeNum ); + TriangulateTraceNode_r( skyboxNodeNum ); + + /* emit some stats */ + //% Sys_FPrintf( SYS_VRB, "%9d original triangles\n", numOriginalTriangles ); + Sys_FPrintf( SYS_VRB, "%9d trace windings (%.2fMB)\n", numTraceWindings, (float) ( numTraceWindings * sizeof( *traceWindings ) ) / ( 1024.0f * 1024.0f ) ); + Sys_FPrintf( SYS_VRB, "%9d trace triangles (%.2fMB)\n", numTraceTriangles, (float) ( numTraceTriangles * sizeof( *traceTriangles ) ) / ( 1024.0f * 1024.0f ) ); + Sys_FPrintf( SYS_VRB, "%9d trace nodes (%.2fMB)\n", numTraceNodes, (float) ( numTraceNodes * sizeof( *traceNodes ) ) / ( 1024.0f * 1024.0f ) ); + Sys_FPrintf( SYS_VRB, "%9d leaf nodes (%.2fMB)\n", numTraceLeafNodes, (float) ( numTraceLeafNodes * sizeof( *traceNodes ) ) / ( 1024.0f * 1024.0f ) ); + //% Sys_FPrintf( SYS_VRB, "%9d average triangles per leaf node\n", numTraceTriangles / numTraceLeafNodes ); + Sys_FPrintf( SYS_VRB, "%9d average windings per leaf node\n", numTraceWindings / ( numTraceLeafNodes + 1 ) ); + Sys_FPrintf( SYS_VRB, "%9d max trace depth\n", maxTraceDepth ); + + /* free trace windings */ + free( traceWindings ); + numTraceWindings = 0; + maxTraceWindings = 0; + deadWinding = -1; + + /* debug code: write out trace triangles to an alias obj file */ + #if 0 + { + int i, j; + FILE *file; + char filename[ 1024 ]; + traceWinding_t *tw; + + + /* open the file */ + strcpy( filename, source ); + StripExtension( filename ); + strcat( filename, ".lin" ); + Sys_Printf( "Opening light trace file %s...\n", filename ); + file = fopen( filename, "w" ); + if ( file == NULL ) { + Error( "Error opening %s for writing", filename ); + } + + /* walk node list */ + for ( i = 0; i < numTraceWindings; i++ ) + { + tw = &traceWindings[ i ]; + for ( j = 0; j < tw->numVerts + 1; j++ ) + fprintf( file, "%f %f %f\n", + tw->v[ j % tw->numVerts ].xyz[ 0 ], tw->v[ j % tw->numVerts ].xyz[ 1 ], tw->v[ j % tw->numVerts ].xyz[ 2 ] ); + } + + /* close it */ + fclose( file ); + } + #endif +} + + + +/* ------------------------------------------------------------------------------- + + raytracer + + ------------------------------------------------------------------------------- */ + +/* + TraceTriangle() + based on code written by william 'spog' joseph + based on code originally written by tomas moller and ben trumbore, journal of graphics tools, 2(1):21-28, 1997 + */ + +#define BARY_EPSILON 0.01f +#define ASLF_EPSILON 0.0001f /* so to not get double shadows */ +#define COPLANAR_EPSILON 0.25f //% 0.000001f +#define NEAR_SHADOW_EPSILON 1.5f //% 1.25f +#define SELF_SHADOW_EPSILON 0.5f + +qboolean TraceTriangle( traceInfo_t *ti, traceTriangle_t *tt, trace_t *trace ){ + int i; + float tvec[ 3 ], pvec[ 3 ], qvec[ 3 ]; + float det, invDet, depth; + float u, v, w, s, t; + int is, it; + byte *pixel; + float shadow; + shaderInfo_t *si; + + + /* don't double-trace against sky */ + si = ti->si; + if ( trace->compileFlags & si->compileFlags & C_SKY ) { + return qfalse; + } + + /* receive shadows from worldspawn group only */ + if ( trace->recvShadows == 1 ) { + if ( ti->castShadows != 1 ) { + return qfalse; + } + } + + /* receive shadows from same group and worldspawn group */ + else if ( trace->recvShadows > 1 ) { + if ( ti->castShadows != 1 && abs( ti->castShadows ) != abs( trace->recvShadows ) ) { + return qfalse; + } + //% Sys_Printf( "%d:%d ", tt->castShadows, trace->recvShadows ); + } + + /* receive shadows from the same group only (< 0) */ + else + { + if ( abs( ti->castShadows ) != abs( trace->recvShadows ) ) { + return qfalse; + } + } + + /* skip patches when doing the grid (FIXME this is an ugly hack) */ + if ( inGrid ) { + if ( ti->skipGrid ) { + return qfalse; + } + } + + /* begin calculating determinant - also used to calculate u parameter */ + CrossProduct( trace->direction, tt->edge2, pvec ); + + /* if determinant is near zero, trace lies in plane of triangle */ + det = DotProduct( tt->edge1, pvec ); + + /* the non-culling branch */ + if ( fabs( det ) < COPLANAR_EPSILON ) { + return qfalse; + } + invDet = 1.0f / det; + + /* calculate distance from first vertex to ray origin */ + VectorSubtract( trace->origin, tt->v[ 0 ].xyz, tvec ); + + /* calculate u parameter and test bounds */ + u = DotProduct( tvec, pvec ) * invDet; + if ( u < -BARY_EPSILON || u > ( 1.0f + BARY_EPSILON ) ) { + return qfalse; + } + + /* prepare to test v parameter */ + CrossProduct( tvec, tt->edge1, qvec ); + + /* calculate v parameter and test bounds */ + v = DotProduct( trace->direction, qvec ) * invDet; + if ( v < -BARY_EPSILON || ( u + v ) > ( 1.0f + BARY_EPSILON ) ) { + return qfalse; + } + + /* calculate t (depth) */ + depth = DotProduct( tt->edge2, qvec ) * invDet; + if ( depth <= trace->inhibitRadius || depth >= trace->distance ) { + return qfalse; + } + + /* if hitpoint is really close to trace origin (sample point), then check for self-shadowing */ + if ( depth <= SELF_SHADOW_EPSILON ) { + /* don't self-shadow */ + for ( i = 0; i < trace->numSurfaces; i++ ) + { + if ( ti->surfaceNum == trace->surfaces[ i ] ) { + return qfalse; + } + } + } + + /* stack compile flags */ + trace->compileFlags |= si->compileFlags; + + /* don't trace against sky */ + if ( si->compileFlags & C_SKY ) { + return qfalse; + } + + /* most surfaces are completely opaque */ + if ( !( si->compileFlags & ( C_ALPHASHADOW | C_LIGHTFILTER ) ) || + si->lightImage == NULL || si->lightImage->pixels == NULL ) { + VectorMA( trace->origin, depth, trace->direction, trace->hit ); + VectorClear( trace->color ); + trace->opaque = qtrue; + return qtrue; + } + + /* force subsampling because the lighting is texture dependent */ + trace->forceSubsampling = 1.0; + + /* try to avoid double shadows near triangle seams */ + if ( u < -ASLF_EPSILON || u > ( 1.0f + ASLF_EPSILON ) || + v < -ASLF_EPSILON || ( u + v ) > ( 1.0f + ASLF_EPSILON ) ) { + return qfalse; + } + + /* calculate w parameter */ + w = 1.0f - ( u + v ); + + /* calculate st from uvw (barycentric) coordinates */ + s = w * tt->v[ 0 ].st[ 0 ] + u * tt->v[ 1 ].st[ 0 ] + v * tt->v[ 2 ].st[ 0 ]; + t = w * tt->v[ 0 ].st[ 1 ] + u * tt->v[ 1 ].st[ 1 ] + v * tt->v[ 2 ].st[ 1 ]; + s = s - floor( s ); + t = t - floor( t ); + is = s * si->lightImage->width; + it = t * si->lightImage->height; + if ( is < 0 ) { + is = 0; + } + if ( is > si->lightImage->width - 1 ) { + is = si->lightImage->width - 1; + } + if ( it < 0 ) { + it = 0; + } + if ( it > si->lightImage->height - 1 ) { + it = si->lightImage->height - 1; + } + + /* get pixel */ + pixel = si->lightImage->pixels + 4 * ( it * si->lightImage->width + is ); + + /* ydnar: color filter */ + if ( si->compileFlags & C_LIGHTFILTER ) { + /* filter by texture color */ + trace->color[ 0 ] *= ( ( 1.0f / 255.0f ) * pixel[ 0 ] ); + trace->color[ 1 ] *= ( ( 1.0f / 255.0f ) * pixel[ 1 ] ); + trace->color[ 2 ] *= ( ( 1.0f / 255.0f ) * pixel[ 2 ] ); + } + + /* ydnar: alpha filter */ + if ( si->compileFlags & C_ALPHASHADOW ) { + /* filter by inverse texture alpha */ + shadow = ( 1.0f / 255.0f ) * ( 255 - pixel[ 3 ] ); + trace->color[ 0 ] *= shadow; + trace->color[ 1 ] *= shadow; + trace->color[ 2 ] *= shadow; + } + + /* check filter for opaque */ + if ( trace->color[ 0 ] <= 0.001f && trace->color[ 1 ] <= 0.001f && trace->color[ 2 ] <= 0.001f ) { + VectorClear( trace->color ); + VectorMA( trace->origin, depth, trace->direction, trace->hit ); + trace->opaque = qtrue; + return qtrue; + } + + /* continue tracing */ + return qfalse; +} + + + +/* + TraceWinding() - ydnar + temporary hack + */ + +qboolean TraceWinding( traceWinding_t *tw, trace_t *trace ){ + int i; + traceTriangle_t tt; + + + /* initial setup */ + tt.infoNum = tw->infoNum; + tt.v[ 0 ] = tw->v[ 0 ]; + + /* walk vertex list */ + for ( i = 1; i + 1 < tw->numVerts; i++ ) + { + /* set verts */ + tt.v[ 1 ] = tw->v[ i ]; + tt.v[ 2 ] = tw->v[ i + 1 ]; + + /* find vectors for two edges sharing the first vert */ + VectorSubtract( tt.v[ 1 ].xyz, tt.v[ 0 ].xyz, tt.edge1 ); + VectorSubtract( tt.v[ 2 ].xyz, tt.v[ 0 ].xyz, tt.edge2 ); + + /* trace it */ + if ( TraceTriangle( &traceInfos[ tt.infoNum ], &tt, trace ) ) { + return qtrue; + } + } + + /* done */ + return qfalse; +} + + + + +/* + TraceLine_r() + returns qtrue if something is hit and tracing can stop + */ + +static qboolean TraceLine_r( int nodeNum, vec3_t origin, vec3_t end, trace_t *trace ){ + traceNode_t *node; + int side; + float front, back, frac; + vec3_t mid; + qboolean r; + + + /* bogus node number means solid, end tracing unless testing all */ + if ( nodeNum < 0 ) { + VectorCopy( origin, trace->hit ); + trace->passSolid = qtrue; + return qtrue; + } + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* solid? */ + if ( node->type == TRACE_LEAF_SOLID ) { + VectorCopy( origin, trace->hit ); + trace->passSolid = qtrue; + return qtrue; + } + + /* leafnode? */ + if ( node->type < 0 ) { + /* note leaf and return */ + if ( node->numItems > 0 && trace->numTestNodes < MAX_TRACE_TEST_NODES ) { + trace->testNodes[ trace->numTestNodes++ ] = nodeNum; + } + return qfalse; + } + + /* ydnar 2003-09-07: don't test branches of the bsp with nothing in them when testall is enabled */ + if ( trace->testAll && node->numItems == 0 ) { + return qfalse; + } + + /* classify beginning and end points */ + switch ( node->type ) + { + case PLANE_X: + front = origin[ 0 ] - node->plane[ 3 ]; + back = end[ 0 ] - node->plane[ 3 ]; + break; + + case PLANE_Y: + front = origin[ 1 ] - node->plane[ 3 ]; + back = end[ 1 ] - node->plane[ 3 ]; + break; + + case PLANE_Z: + front = origin[ 2 ] - node->plane[ 3 ]; + back = end[ 2 ] - node->plane[ 3 ]; + break; + + default: + front = DotProduct( origin, node->plane ) - node->plane[ 3 ]; + back = DotProduct( end, node->plane ) - node->plane[ 3 ]; + break; + } + + /* entirely in front side? */ + if ( front >= -TRACE_ON_EPSILON && back >= -TRACE_ON_EPSILON ) { + return TraceLine_r( node->children[ 0 ], origin, end, trace ); + } + + /* entirely on back side? */ + if ( front < TRACE_ON_EPSILON && back < TRACE_ON_EPSILON ) { + return TraceLine_r( node->children[ 1 ], origin, end, trace ); + } + + /* select side */ + side = front < 0; + + /* calculate intercept point */ + frac = front / ( front - back ); + mid[ 0 ] = origin[ 0 ] + ( end[ 0 ] - origin[ 0 ] ) * frac; + mid[ 1 ] = origin[ 1 ] + ( end[ 1 ] - origin[ 1 ] ) * frac; + mid[ 2 ] = origin[ 2 ] + ( end[ 2 ] - origin[ 2 ] ) * frac; + + /* fixme: check inhibit radius, then solid nodes and ignore */ + + /* set trace hit here */ + //% VectorCopy( mid, trace->hit ); + + /* trace first side */ + r = TraceLine_r( node->children[ side ], origin, mid, trace ); + if ( r ) { + return r; + } + + /* trace other side */ + return TraceLine_r( node->children[ !side ], mid, end, trace ); +} + + + +/* + TraceLine() - ydnar + rewrote this function a bit :) + */ + +void TraceLine( trace_t *trace ){ + int i, j; + traceNode_t *node; + traceTriangle_t *tt; + traceInfo_t *ti; + + + /* setup output (note: this code assumes the input data is completely filled out) */ + trace->passSolid = qfalse; + trace->opaque = qfalse; + trace->compileFlags = 0; + trace->numTestNodes = 0; + + /* early outs */ + if ( !trace->recvShadows || !trace->testOcclusion || trace->distance <= 0.00001f ) { + return; + } + + /* trace through nodes */ + TraceLine_r( headNodeNum, trace->origin, trace->end, trace ); + if ( trace->passSolid && !trace->testAll ) { + trace->opaque = qtrue; + return; + } + + /* skip surfaces? */ + if ( noSurfaces ) { + return; + } + + /* testall means trace through sky */ + if ( trace->testAll && trace->numTestNodes < MAX_TRACE_TEST_NODES && + trace->compileFlags & C_SKY && + ( trace->numSurfaces == 0 || surfaceInfos[ trace->surfaces[ 0 ] ].childSurfaceNum < 0 ) ) { + //% trace->testNodes[ trace->numTestNodes++ ] = skyboxNodeNum; + TraceLine_r( skyboxNodeNum, trace->origin, trace->end, trace ); + } + + /* walk node list */ + for ( i = 0; i < trace->numTestNodes; i++ ) + { + /* get node */ + node = &traceNodes[ trace->testNodes[ i ] ]; + + /* walk node item list */ + for ( j = 0; j < node->numItems; j++ ) + { + tt = &traceTriangles[ node->items[ j ] ]; + ti = &traceInfos[ tt->infoNum ]; + if ( TraceTriangle( ti, tt, trace ) ) { + return; + } + //% if( TraceWinding( &traceWindings[ node->items[ j ] ], trace ) ) + //% return; + } + } +} + + + +/* + SetupTrace() - ydnar + sets up certain trace values + */ + +float SetupTrace( trace_t *trace ){ + VectorSubtract( trace->end, trace->origin, trace->displacement ); + trace->distance = VectorNormalize( trace->displacement, trace->direction ); + VectorCopy( trace->origin, trace->hit ); + return trace->distance; +} diff --git a/tools/vmap/light_ydnar.c b/tools/vmap/light_ydnar.c new file mode 100644 index 0000000..6da91ef --- /dev/null +++ b/tools/vmap/light_ydnar.c @@ -0,0 +1,4576 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_YDNAR_C + + + +/* dependencies */ +#include "vmap.h" + + + + +/* + ColorToBytes() + ydnar: moved to here 2001-02-04 + */ + +void ColorToBytes( const float *color, byte *colorBytes, float scale ){ + int i; + float max, gamma; + vec3_t sample; + float inv, dif; + + + /* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */ + if ( scale <= 0.0f ) { + scale = 1.0f; + } + + /* make a local copy */ + VectorScale( color, scale, sample ); + + /* muck with it */ + gamma = 1.0f / lightmapGamma; + for ( i = 0; i < 3; i++ ) + { + /* handle negative light */ + if ( sample[ i ] < 0.0f ) { + sample[ i ] = 0.0f; + continue; + } + + /* gamma */ + sample[ i ] = pow( sample[ i ] / 255.0f, gamma ) * 255.0f; + } + + if ( lightmapExposure == 0 ) { + /* clamp with color normalization */ + max = sample[ 0 ]; + if ( sample[ 1 ] > max ) { + max = sample[ 1 ]; + } + if ( sample[ 2 ] > max ) { + max = sample[ 2 ]; + } + if ( max > 255.0f ) { + VectorScale( sample, ( 255.0f / max ), sample ); + } + } + else + { + inv = 1.f / lightmapExposure; + //Exposure + + max = sample[ 0 ]; + if ( sample[ 1 ] > max ) { + max = sample[ 1 ]; + } + if ( sample[ 2 ] > max ) { + max = sample[ 2 ]; + } + + dif = ( 1 - exp( -max * inv ) ) * 255; + + if ( max > 0 ) { + dif = dif / max; + } + else + { + dif = 0; + } + + for ( i = 0; i < 3; i++ ) + { + sample[i] *= dif; + } + } + + + /* compensate for ingame overbrighting/bitshifting */ + VectorScale( sample, ( 1.0f / lightmapCompensate ), sample ); + + /* sRGB lightmaps */ + if ( lightmapsRGB ) { + sample[0] = floor( Image_sRGBFloatFromLinearFloat( sample[0] * ( 1.0 / 255.0 ) ) * 255.0 + 0.5 ); + sample[1] = floor( Image_sRGBFloatFromLinearFloat( sample[1] * ( 1.0 / 255.0 ) ) * 255.0 + 0.5 ); + sample[2] = floor( Image_sRGBFloatFromLinearFloat( sample[2] * ( 1.0 / 255.0 ) ) * 255.0 + 0.5 ); + } + + /* store it off */ + colorBytes[ 0 ] = sample[ 0 ]; + colorBytes[ 1 ] = sample[ 1 ]; + colorBytes[ 2 ] = sample[ 2 ]; +} + + +void ColorToHDR( const float *color, float *colorBytes ){ + int i; + float max, gamma; + vec3_t sample; + float inv, dif; + float scale = 1.0; + + + /* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */ + if ( scale <= 0.0f ) { + scale = 1.0f; + } + + /* make a local copy */ + VectorScale( color, scale, sample ); + + /* muck with it */ + gamma = 1.0f / lightmapGamma; + for ( i = 0; i < 3; i++ ) + { + /* handle negative light */ + if ( sample[ i ] < 0.0f ) { + sample[ i ] = 0.0f; + continue; + } + + /* gamma */ + sample[ i ] = pow( sample[ i ] / 255.0f, gamma ); + } + + if ( lightmapExposure == 0 ) { + /* clamp with color normalization */ + max = sample[ 0 ]; + if ( sample[ 1 ] > max ) { + max = sample[ 1 ]; + } + if ( sample[ 2 ] > max ) { + max = sample[ 2 ]; + } + } + else + { + inv = 1.f / lightmapExposure; + //Exposure + + max = sample[ 0 ]; + if ( sample[ 1 ] > max ) { + max = sample[ 1 ]; + } + if ( sample[ 2 ] > max ) { + max = sample[ 2 ]; + } + + dif = ( 1 - exp( -max * inv ) ) * 255; + + if ( max > 0 ) { + dif = dif / max; + } + else + { + dif = 0; + } + + for ( i = 0; i < 3; i++ ) + { + sample[i] *= dif; + } + } + + + /* compensate for ingame overbrighting/bitshifting */ + VectorScale( sample, ( 1.0f / lightmapCompensate ), sample ); + + /* sRGB lightmaps */ + if ( lightmapsRGB ) { + sample[0] = Image_sRGBFloatFromLinearFloat( sample[0] ); + sample[1] = Image_sRGBFloatFromLinearFloat( sample[1] ); + sample[2] = Image_sRGBFloatFromLinearFloat( sample[2] ); + } + + /* store it off */ + colorBytes[ 0 ] = sample[ 0 ]; + colorBytes[ 1 ] = sample[ 1 ]; + colorBytes[ 2 ] = sample[ 2 ]; +} + + + +/* ------------------------------------------------------------------------------- + + this section deals with phong shading (normal interpolation across brush faces) + + ------------------------------------------------------------------------------- */ + +/* + SmoothNormals() + smooths together coincident vertex normals across the bsp + */ + +#define MAX_SAMPLES 256 +#define THETA_EPSILON 0.000001 +#define EQUAL_NORMAL_EPSILON 0.01 + +void SmoothNormals( void ){ + int i, j, k, f, cs, numVerts, numVotes, fOld, start; + float shadeAngle, defaultShadeAngle, maxShadeAngle, dot, testAngle; + bspDrawSurface_t *ds; + shaderInfo_t *si; + float *shadeAngles; + byte *smoothed; + vec3_t average, diff; + int indexes[ MAX_SAMPLES ]; + vec3_t votes[ MAX_SAMPLES ]; + + + /* allocate shade angle table */ + shadeAngles = safe_malloc( numBSPDrawVerts * sizeof( float ) ); + memset( shadeAngles, 0, numBSPDrawVerts * sizeof( float ) ); + + /* allocate smoothed table */ + cs = ( numBSPDrawVerts / 8 ) + 1; + smoothed = safe_malloc( cs ); + memset( smoothed, 0, cs ); + + /* set default shade angle */ + defaultShadeAngle = DEG2RAD( shadeAngleDegrees ); + maxShadeAngle = 0; + + /* run through every surface and flag verts belonging to non-lightmapped surfaces + and set per-vertex smoothing angle */ + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get drawsurf */ + ds = &bspDrawSurfaces[ i ]; + + /* get shader for shade angle */ + si = surfaceInfos[ i ].si; + if ( si->shadeAngleDegrees ) { + shadeAngle = DEG2RAD( si->shadeAngleDegrees ); + } + else{ + shadeAngle = defaultShadeAngle; + } + if ( shadeAngle > maxShadeAngle ) { + maxShadeAngle = shadeAngle; + } + + /* flag its verts */ + for ( j = 0; j < ds->numVerts; j++ ) + { + f = ds->firstVert + j; + shadeAngles[ f ] = shadeAngle; + if ( ds->surfaceType == MST_TRIANGLE_SOUP ) { + smoothed[ f >> 3 ] |= ( 1 << ( f & 7 ) ); + } + } + + /* ydnar: optional force-to-trisoup */ + if ( trisoup && ds->surfaceType == MST_PLANAR ) { + ds->surfaceType = MST_TRIANGLE_SOUP; + ds->lightmapNum[ 0 ] = -3; + } + } + + /* bail if no surfaces have a shade angle */ + if ( maxShadeAngle == 0 ) { + free( shadeAngles ); + free( smoothed ); + return; + } + + /* init pacifier */ + fOld = -1; + start = I_FloatTime(); + + /* go through the list of vertexes */ + for ( i = 0; i < numBSPDrawVerts; i++ ) + { + /* print pacifier */ + f = 10 * i / numBSPDrawVerts; + if ( f != fOld ) { + fOld = f; + Sys_Printf( "%i...", f ); + } + + /* already smoothed? */ + if ( smoothed[ i >> 3 ] & ( 1 << ( i & 7 ) ) ) { + continue; + } + + /* clear */ + VectorClear( average ); + numVerts = 0; + numVotes = 0; + + /* build a table of coincident vertexes */ + for ( j = i; j < numBSPDrawVerts && numVerts < MAX_SAMPLES; j++ ) + { + /* already smoothed? */ + if ( smoothed[ j >> 3 ] & ( 1 << ( j & 7 ) ) ) { + continue; + } + + /* test vertexes */ + if ( VectorCompare( yDrawVerts[ i ].xyz, yDrawVerts[ j ].xyz ) == qfalse ) { + continue; + } + + /* use smallest shade angle */ + shadeAngle = ( shadeAngles[ i ] < shadeAngles[ j ] ? shadeAngles[ i ] : shadeAngles[ j ] ); + + /* check shade angle */ + dot = DotProduct( bspDrawVerts[ i ].normal, bspDrawVerts[ j ].normal ); + if ( dot > 1.0 ) { + dot = 1.0; + } + else if ( dot < -1.0 ) { + dot = -1.0; + } + testAngle = acos( dot ) + THETA_EPSILON; + if ( testAngle >= shadeAngle ) { + //Sys_Printf( "F(%3.3f >= %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) ); + continue; + } + //Sys_Printf( "P(%3.3f < %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) ); + + /* add to the list */ + indexes[ numVerts++ ] = j; + + /* flag vertex */ + smoothed[ j >> 3 ] |= ( 1 << ( j & 7 ) ); + + /* see if this normal has already been voted */ + for ( k = 0; k < numVotes; k++ ) + { + VectorSubtract( bspDrawVerts[ j ].normal, votes[ k ], diff ); + if ( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON && + fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON && + fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON ) { + break; + } + } + + /* add a new vote? */ + if ( k == numVotes && numVotes < MAX_SAMPLES ) { + VectorAdd( average, bspDrawVerts[ j ].normal, average ); + VectorCopy( bspDrawVerts[ j ].normal, votes[ numVotes ] ); + numVotes++; + } + } + + /* don't average for less than 2 verts */ + if ( numVerts < 2 ) { + continue; + } + + /* average normal */ + if ( VectorNormalize( average, average ) > 0 ) { + /* smooth */ + for ( j = 0; j < numVerts; j++ ) + VectorCopy( average, yDrawVerts[ indexes[ j ] ].normal ); + } + } + + /* free the tables */ + free( shadeAngles ); + free( smoothed ); + + /* print time */ + Sys_Printf( " (%i)\n", (int) ( I_FloatTime() - start ) ); +} + + + +/* ------------------------------------------------------------------------------- + + this section deals with phong shaded lightmap tracing + + ------------------------------------------------------------------------------- */ + +/* 9th rewrite (recursive subdivision of a lightmap triangle) */ + +/* + CalcTangentVectors() + calculates the st tangent vectors for normalmapping + */ + +static qboolean CalcTangentVectors( int numVerts, bspDrawVert_t **dv, vec3_t *stv, vec3_t *ttv ){ + int i; + float bb, s, t; + vec3_t bary; + + + /* calculate barycentric basis for the triangle */ + bb = ( dv[ 1 ]->st[ 0 ] - dv[ 0 ]->st[ 0 ] ) * ( dv[ 2 ]->st[ 1 ] - dv[ 0 ]->st[ 1 ] ) - ( dv[ 2 ]->st[ 0 ] - dv[ 0 ]->st[ 0 ] ) * ( dv[ 1 ]->st[ 1 ] - dv[ 0 ]->st[ 1 ] ); + if ( fabs( bb ) < 0.00000001f ) { + return qfalse; + } + + /* do each vertex */ + for ( i = 0; i < numVerts; i++ ) + { + /* calculate s tangent vector */ + s = dv[ i ]->st[ 0 ] + 10.0f; + t = dv[ i ]->st[ 1 ]; + bary[ 0 ] = ( ( dv[ 1 ]->st[ 0 ] - s ) * ( dv[ 2 ]->st[ 1 ] - t ) - ( dv[ 2 ]->st[ 0 ] - s ) * ( dv[ 1 ]->st[ 1 ] - t ) ) / bb; + bary[ 1 ] = ( ( dv[ 2 ]->st[ 0 ] - s ) * ( dv[ 0 ]->st[ 1 ] - t ) - ( dv[ 0 ]->st[ 0 ] - s ) * ( dv[ 2 ]->st[ 1 ] - t ) ) / bb; + bary[ 2 ] = ( ( dv[ 0 ]->st[ 0 ] - s ) * ( dv[ 1 ]->st[ 1 ] - t ) - ( dv[ 1 ]->st[ 0 ] - s ) * ( dv[ 0 ]->st[ 1 ] - t ) ) / bb; + + stv[ i ][ 0 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 0 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 0 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 0 ]; + stv[ i ][ 1 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 1 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 1 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 1 ]; + stv[ i ][ 2 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 2 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 2 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 2 ]; + + VectorSubtract( stv[ i ], dv[ i ]->xyz, stv[ i ] ); + VectorNormalize( stv[ i ], stv[ i ] ); + + /* calculate t tangent vector */ + s = dv[ i ]->st[ 0 ]; + t = dv[ i ]->st[ 1 ] + 10.0f; + bary[ 0 ] = ( ( dv[ 1 ]->st[ 0 ] - s ) * ( dv[ 2 ]->st[ 1 ] - t ) - ( dv[ 2 ]->st[ 0 ] - s ) * ( dv[ 1 ]->st[ 1 ] - t ) ) / bb; + bary[ 1 ] = ( ( dv[ 2 ]->st[ 0 ] - s ) * ( dv[ 0 ]->st[ 1 ] - t ) - ( dv[ 0 ]->st[ 0 ] - s ) * ( dv[ 2 ]->st[ 1 ] - t ) ) / bb; + bary[ 2 ] = ( ( dv[ 0 ]->st[ 0 ] - s ) * ( dv[ 1 ]->st[ 1 ] - t ) - ( dv[ 1 ]->st[ 0 ] - s ) * ( dv[ 0 ]->st[ 1 ] - t ) ) / bb; + + ttv[ i ][ 0 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 0 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 0 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 0 ]; + ttv[ i ][ 1 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 1 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 1 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 1 ]; + ttv[ i ][ 2 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 2 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 2 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 2 ]; + + VectorSubtract( ttv[ i ], dv[ i ]->xyz, ttv[ i ] ); + VectorNormalize( ttv[ i ], ttv[ i ] ); + + /* debug code */ + //% Sys_FPrintf( SYS_VRB, "%d S: (%f %f %f) T: (%f %f %f)\n", i, + //% stv[ i ][ 0 ], stv[ i ][ 1 ], stv[ i ][ 2 ], ttv[ i ][ 0 ], ttv[ i ][ 1 ], ttv[ i ][ 2 ] ); + } + + /* return to caller */ + return qtrue; +} + + + + +/* + PerturbNormal() + perterbs the normal by the shader's normalmap in tangent space + */ + +static void PerturbNormal( bspDrawVert_t *dv, shaderInfo_t *si, vec3_t pNormal, vec3_t stv[ 3 ], vec3_t ttv[ 3 ] ){ + int i; + vec4_t bump; + + + /* passthrough */ + VectorCopy( dv->normal, pNormal ); + + /* sample normalmap */ + if ( RadSampleImage( si->normalImage->pixels, si->normalImage->width, si->normalImage->height, dv->st, bump ) == qfalse ) { + return; + } + + /* remap sampled normal from [0,255] to [-1,-1] */ + for ( i = 0; i < 3; i++ ) + bump[ i ] = ( bump[ i ] - 127.0f ) * ( 1.0f / 127.5f ); + + /* scale tangent vectors and add to original normal */ + VectorMA( dv->normal, bump[ 0 ], stv[ 0 ], pNormal ); + VectorMA( pNormal, bump[ 1 ], ttv[ 0 ], pNormal ); + VectorMA( pNormal, bump[ 2 ], dv->normal, pNormal ); + + /* renormalize and return */ + VectorNormalize( pNormal, pNormal ); +} + + + +/* + MapSingleLuxel() + maps a luxel for triangle bv at + */ + +#define NUDGE 0.5f +#define BOGUS_NUDGE -99999.0f + +static int MapSingleLuxel( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv, vec4_t plane, float pass, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] ){ + int i, x, y, numClusters, *clusters, pointCluster, *cluster; + float *luxel, *origin, *normal, d, lightmapSampleOffset; + shaderInfo_t *si; + vec3_t pNormal; + vec3_t vecs[ 3 ]; + vec3_t nudged; + vec3_t cverts[ 3 ]; + vec3_t temp; + vec4_t sideplane, hostplane; + vec3_t origintwo; + int j, next; + float e; + float *nudge; + static float nudges[][ 2 ] = + { + //%{ 0, 0 }, /* try center first */ + { -NUDGE, 0 }, /* left */ + { NUDGE, 0 }, /* right */ + { 0, NUDGE }, /* up */ + { 0, -NUDGE }, /* down */ + { -NUDGE, NUDGE }, /* left/up */ + { NUDGE, -NUDGE }, /* right/down */ + { NUDGE, NUDGE }, /* right/up */ + { -NUDGE, -NUDGE }, /* left/down */ + { BOGUS_NUDGE, BOGUS_NUDGE } + }; + + + /* find luxel xy coords (fixme: subtract 0.5?) */ + x = dv->lightmap[ 0 ][ 0 ]; + y = dv->lightmap[ 0 ][ 1 ]; + if ( x < 0 ) { + x = 0; + } + else if ( x >= lm->sw ) { + x = lm->sw - 1; + } + if ( y < 0 ) { + y = 0; + } + else if ( y >= lm->sh ) { + y = lm->sh - 1; + } + + /* set shader and cluster list */ + if ( info != NULL ) { + si = info->si; + numClusters = info->numSurfaceClusters; + clusters = &surfaceClusters[ info->firstSurfaceCluster ]; + } + else + { + si = NULL; + numClusters = 0; + clusters = NULL; + } + + /* get luxel, origin, cluster, and normal */ + luxel = SUPER_LUXEL( 0, x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* don't attempt to remap occluded luxels for planar surfaces */ + if ( ( *cluster ) == CLUSTER_OCCLUDED && lm->plane != NULL ) { + return ( *cluster ); + } + + /* only average the normal for premapped luxels */ + else if ( ( *cluster ) >= 0 ) { + /* do bumpmap calculations */ + if ( stv != NULL ) { + PerturbNormal( dv, si, pNormal, stv, ttv ); + } + else{ + VectorCopy( dv->normal, pNormal ); + } + + /* add the additional normal data */ + VectorAdd( normal, pNormal, normal ); + luxel[ 3 ] += 1.0f; + return ( *cluster ); + } + + /* otherwise, unmapped luxels (*cluster == CLUSTER_UNMAPPED) will have their full attributes calculated */ + + /* get origin */ + + /* axial lightmap projection */ + if ( lm->vecs != NULL ) { + /* calculate an origin for the sample from the lightmap vectors */ + VectorCopy( lm->origin, origin ); + for ( i = 0; i < 3; i++ ) + { + /* add unless it's the axis, which is taken care of later */ + if ( i == lm->axisNum ) { + continue; + } + origin[ i ] += ( x * lm->vecs[ 0 ][ i ] ) + ( y * lm->vecs[ 1 ][ i ] ); + } + + /* project the origin onto the plane */ + d = DotProduct( origin, plane ) - plane[ 3 ]; + d /= plane[ lm->axisNum ]; + origin[ lm->axisNum ] -= d; + } + + /* non axial lightmap projection (explicit xyz) */ + else{ + VectorCopy( dv->xyz, origin ); + } + + ////////////////////// + //27's test to make sure samples stay within the triangle boundaries + //1) Test the sample origin to see if it lays on the wrong side of any edge (x/y) + //2) if it does, nudge it onto the correct side. + + if ( worldverts != NULL && lightmapTriangleCheck ) { + for ( j = 0; j < 3; j++ ) + { + VectorCopy( worldverts[j],cverts[j] ); + } + PlaneFromPoints( hostplane,cverts[0],cverts[1],cverts[2] ); + + for ( j = 0; j < 3; j++ ) + { + for ( i = 0; i < 3; i++ ) + { + //build plane using 2 edges and a normal + next = ( i + 1 ) % 3; + + VectorCopy( cverts[next],temp ); + VectorAdd( temp,hostplane,temp ); + PlaneFromPoints( sideplane,cverts[i],cverts[ next ], temp ); + + //planetest sample point + e = DotProduct( origin,sideplane ); + e = e - sideplane[3]; + if ( e > 0 ) { + //we're bad. + //VectorClear(origin); + //Move the sample point back inside triangle bounds + origin[0] -= sideplane[0] * ( e + 1 ); + origin[1] -= sideplane[1] * ( e + 1 ); + origin[2] -= sideplane[2] * ( e + 1 ); +#ifdef DEBUG_27_1 + VectorClear( origin ); +#endif + } + } + } + } + + //////////////////////// + + /* planar surfaces have precalculated lightmap vectors for nudging */ + if ( lm->plane != NULL ) { + VectorCopy( lm->vecs[ 0 ], vecs[ 0 ] ); + VectorCopy( lm->vecs[ 1 ], vecs[ 1 ] ); + VectorCopy( lm->plane, vecs[ 2 ] ); + } + + /* non-planar surfaces must calculate them */ + else + { + if ( plane != NULL ) { + VectorCopy( plane, vecs[ 2 ] ); + } + else{ + VectorCopy( dv->normal, vecs[ 2 ] ); + } + MakeNormalVectors( vecs[ 2 ], vecs[ 0 ], vecs[ 1 ] ); + } + + /* push the origin off the surface a bit */ + if ( si != NULL ) { + lightmapSampleOffset = si->lightmapSampleOffset; + } + else{ + lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET; + } + if ( lm->axisNum < 0 ) { + VectorMA( origin, lightmapSampleOffset, vecs[ 2 ], origin ); + } + else if ( vecs[ 2 ][ lm->axisNum ] < 0.0f ) { + origin[ lm->axisNum ] -= lightmapSampleOffset; + } + else{ + origin[ lm->axisNum ] += lightmapSampleOffset; + } + + VectorCopy( origin,origintwo ); + if ( lightmapExtraVisClusterNudge ) { + origintwo[0] += vecs[2][0]; + origintwo[1] += vecs[2][1]; + origintwo[2] += vecs[2][2]; + } + + /* get cluster */ + pointCluster = ClusterForPointExtFilter( origintwo, LUXEL_EPSILON, numClusters, clusters ); + + /* another retarded hack, storing nudge count in luxel[ 1 ] */ + luxel[ 1 ] = 0.0f; + + /* point in solid? (except in dark mode) */ + if ( pointCluster < 0 && dark == qfalse ) { + /* nudge the the location around */ + nudge = nudges[ 0 ]; + while ( nudge[ 0 ] > BOGUS_NUDGE && pointCluster < 0 ) + { + /* nudge the vector around a bit */ + for ( i = 0; i < 3; i++ ) + { + /* set nudged point*/ + nudged[ i ] = origintwo[ i ] + ( nudge[ 0 ] * vecs[ 0 ][ i ] ) + ( nudge[ 1 ] * vecs[ 1 ][ i ] ); + } + nudge += 2; + + /* get pvs cluster */ + pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); //% + 0.625 ); + if ( pointCluster >= 0 ) { + VectorCopy( nudged, origin ); + } + luxel[ 1 ] += 1.0f; + } + } + + /* as a last resort, if still in solid, try drawvert origin offset by normal (except in dark mode) */ + if ( pointCluster < 0 && si != NULL && dark == qfalse ) { + VectorMA( dv->xyz, lightmapSampleOffset, dv->normal, nudged ); + pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); + if ( pointCluster >= 0 ) { + VectorCopy( nudged, origin ); + } + luxel[ 1 ] += 1.0f; + } + + /* valid? */ + if ( pointCluster < 0 ) { + ( *cluster ) = CLUSTER_OCCLUDED; + VectorClear( origin ); + VectorClear( normal ); + numLuxelsOccluded++; + return ( *cluster ); + } + + /* debug code */ + //% Sys_Printf( "%f %f %f\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] ); + + /* do bumpmap calculations */ + if ( stv ) { + PerturbNormal( dv, si, pNormal, stv, ttv ); + } + else{ + VectorCopy( dv->normal, pNormal ); + } + + /* store the cluster and normal */ + ( *cluster ) = pointCluster; + VectorCopy( pNormal, normal ); + + /* store explicit mapping pass and implicit mapping pass */ + luxel[ 0 ] = pass; + luxel[ 3 ] = 1.0f; + + /* add to count */ + numLuxelsMapped++; + + /* return ok */ + return ( *cluster ); +} + + + +/* + MapTriangle_r() + recursively subdivides a triangle until its edges are shorter + than the distance between two luxels (thanks jc :) + */ + +static void MapTriangle_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], vec4_t plane, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] ){ + bspDrawVert_t mid, *dv2[ 3 ]; + int max; + + + /* map the vertexes */ + #if 0 + MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv ); + MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv ); + MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv ); + #endif + + /* subdivide calc */ + { + int i; + float *a, *b, dx, dy, dist, maxDist; + + + /* find the longest edge and split it */ + max = -1; + maxDist = 0; + for ( i = 0; i < 3; i++ ) + { + /* get verts */ + a = dv[ i ]->lightmap[ 0 ]; + b = dv[ ( i + 1 ) % 3 ]->lightmap[ 0 ]; + + /* get dists */ + dx = a[ 0 ] - b[ 0 ]; + dy = a[ 1 ] - b[ 1 ]; + dist = ( dx * dx ) + ( dy * dy ); //% sqrt( (dx * dx) + (dy * dy) ); + + /* longer? */ + if ( dist > maxDist ) { + maxDist = dist; + max = i; + } + } + + /* try to early out */ + if ( max < 0 || maxDist <= subdivideThreshold ) { /* ydnar: was i < 0 instead of max < 0 (?) */ + return; + } + } + + /* split the longest edge and map it */ + LerpDrawVert( dv[ max ], dv[ ( max + 1 ) % 3 ], &mid ); + MapSingleLuxel( lm, info, &mid, plane, 1, stv, ttv, worldverts ); + + /* push the point up a little bit to account for fp creep (fixme: revisit this) */ + //% VectorMA( mid.xyz, 2.0f, mid.normal, mid.xyz ); + + /* recurse to first triangle */ + VectorCopy( dv, dv2 ); + dv2[ max ] = ∣ + MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); + + /* recurse to second triangle */ + VectorCopy( dv, dv2 ); + dv2[ ( max + 1 ) % 3 ] = ∣ + MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); +} + + + +/* + MapTriangle() + seed function for MapTriangle_r() + requires a cw ordered triangle + */ + +static qboolean MapTriangle( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], qboolean mapNonAxial ){ + int i; + vec4_t plane; + vec3_t *stv, *ttv, stvStatic[ 3 ], ttvStatic[ 3 ]; + vec3_t worldverts[ 3 ]; + + + /* get plane if possible */ + if ( lm->plane != NULL ) { + VectorCopy( lm->plane, plane ); + plane[ 3 ] = lm->plane[ 3 ]; + } + + /* otherwise make one from the points */ + else if ( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse ) { + return qfalse; + } + + /* check to see if we need to calculate texture->world tangent vectors */ + if ( info->si->normalImage != NULL && CalcTangentVectors( 3, dv, stvStatic, ttvStatic ) ) { + stv = stvStatic; + ttv = ttvStatic; + } + else + { + stv = NULL; + ttv = NULL; + } + + VectorCopy( dv[ 0 ]->xyz, worldverts[ 0 ] ); + VectorCopy( dv[ 1 ]->xyz, worldverts[ 1 ] ); + VectorCopy( dv[ 2 ]->xyz, worldverts[ 2 ] ); + + /* map the vertexes */ + MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, worldverts ); + MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, worldverts ); + MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, worldverts ); + + /* 2002-11-20: prefer axial triangle edges */ + if ( mapNonAxial ) { + /* subdivide the triangle */ + MapTriangle_r( lm, info, dv, plane, stv, ttv, worldverts ); + return qtrue; + } + + for ( i = 0; i < 3; i++ ) + { + float *a, *b; + bspDrawVert_t *dv2[ 3 ]; + + + /* get verts */ + a = dv[ i ]->lightmap[ 0 ]; + b = dv[ ( i + 1 ) % 3 ]->lightmap[ 0 ]; + + /* make degenerate triangles for mapping edges */ + if ( fabs( a[ 0 ] - b[ 0 ] ) < 0.01f || fabs( a[ 1 ] - b[ 1 ] ) < 0.01f ) { + dv2[ 0 ] = dv[ i ]; + dv2[ 1 ] = dv[ ( i + 1 ) % 3 ]; + dv2[ 2 ] = dv[ ( i + 1 ) % 3 ]; + + /* map the degenerate triangle */ + MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); + } + } + + return qtrue; +} + + + +/* + MapQuad_r() + recursively subdivides a quad until its edges are shorter + than the distance between two luxels + */ + +static void MapQuad_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ], vec4_t plane, vec3_t stv[ 4 ], vec3_t ttv[ 4 ] ){ + bspDrawVert_t mid[ 2 ], *dv2[ 4 ]; + int max; + + + /* subdivide calc */ + { + int i; + float *a, *b, dx, dy, dist, maxDist; + + + /* find the longest edge and split it */ + max = -1; + maxDist = 0; + for ( i = 0; i < 4; i++ ) + { + /* get verts */ + a = dv[ i ]->lightmap[ 0 ]; + b = dv[ ( i + 1 ) % 4 ]->lightmap[ 0 ]; + + /* get dists */ + dx = a[ 0 ] - b[ 0 ]; + dy = a[ 1 ] - b[ 1 ]; + dist = ( dx * dx ) + ( dy * dy ); //% sqrt( (dx * dx) + (dy * dy) ); + + /* longer? */ + if ( dist > maxDist ) { + maxDist = dist; + max = i; + } + } + + /* try to early out */ + if ( max < 0 || maxDist <= subdivideThreshold ) { + return; + } + } + + /* we only care about even/odd edges */ + max &= 1; + + /* split the longest edges */ + LerpDrawVert( dv[ max ], dv[ ( max + 1 ) % 4 ], &mid[ 0 ] ); + LerpDrawVert( dv[ max + 2 ], dv[ ( max + 3 ) % 4 ], &mid[ 1 ] ); + + /* map the vertexes */ + MapSingleLuxel( lm, info, &mid[ 0 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, &mid[ 1 ], plane, 1, stv, ttv, NULL ); + + /* 0 and 2 */ + if ( max == 0 ) { + /* recurse to first quad */ + dv2[ 0 ] = dv[ 0 ]; + dv2[ 1 ] = &mid[ 0 ]; + dv2[ 2 ] = &mid[ 1 ]; + dv2[ 3 ] = dv[ 3 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + + /* recurse to second quad */ + dv2[ 0 ] = &mid[ 0 ]; + dv2[ 1 ] = dv[ 1 ]; + dv2[ 2 ] = dv[ 2 ]; + dv2[ 3 ] = &mid[ 1 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + } + + /* 1 and 3 */ + else + { + /* recurse to first quad */ + dv2[ 0 ] = dv[ 0 ]; + dv2[ 1 ] = dv[ 1 ]; + dv2[ 2 ] = &mid[ 0 ]; + dv2[ 3 ] = &mid[ 1 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + + /* recurse to second quad */ + dv2[ 0 ] = &mid[ 1 ]; + dv2[ 1 ] = &mid[ 0 ]; + dv2[ 2 ] = dv[ 2 ]; + dv2[ 3 ] = dv[ 3 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + } +} + + + +/* + MapQuad() + seed function for MapQuad_r() + requires a cw ordered triangle quad + */ + +#define QUAD_PLANAR_EPSILON 0.5f + +static qboolean MapQuad( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ] ){ + float dist; + vec4_t plane; + vec3_t *stv, *ttv, stvStatic[ 4 ], ttvStatic[ 4 ]; + + + /* get plane if possible */ + if ( lm->plane != NULL ) { + VectorCopy( lm->plane, plane ); + plane[ 3 ] = lm->plane[ 3 ]; + } + + /* otherwise make one from the points */ + else if ( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse ) { + return qfalse; + } + + /* 4th point must fall on the plane */ + dist = DotProduct( plane, dv[ 3 ]->xyz ) - plane[ 3 ]; + if ( fabs( dist ) > QUAD_PLANAR_EPSILON ) { + return qfalse; + } + + /* check to see if we need to calculate texture->world tangent vectors */ + if ( info->si->normalImage != NULL && CalcTangentVectors( 4, dv, stvStatic, ttvStatic ) ) { + stv = stvStatic; + ttv = ttvStatic; + } + else + { + stv = NULL; + ttv = NULL; + } + + /* map the vertexes */ + MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, dv[ 3 ], plane, 1, stv, ttv, NULL ); + + /* subdivide the quad */ + MapQuad_r( lm, info, dv, plane, stv, ttv ); + return qtrue; +} + + + +/* + MapRawLightmap() + maps the locations, normals, and pvs clusters for a raw lightmap + */ + +#define VectorDivide( in, d, out ) VectorScale( in, ( 1.0f / ( d ) ), out ) //% (out)[ 0 ] = (in)[ 0 ] / (d), (out)[ 1 ] = (in)[ 1 ] / (d), (out)[ 2 ] = (in)[ 2 ] / (d) + +void MapRawLightmap( int rawLightmapNum ){ + int n, num, i, x, y, sx, sy, pw[ 5 ], r, *cluster, mapNonAxial; + float *luxel, *origin, *normal, samples, radius, pass; + rawLightmap_t *lm; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + mesh_t src, *subdivided, *mesh; + bspDrawVert_t *verts, *dv[ 4 ], fake; + + + /* bail if this number exceeds the number of raw lightmaps */ + if ( rawLightmapNum >= numRawLightmaps ) { + return; + } + + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + /* ----------------------------------------------------------------- + map referenced surfaces onto the raw lightmap + ----------------------------------------------------------------- */ + + /* walk the list of surfaces on this raw lightmap */ + for ( n = 0; n < lm->numLightSurfaces; n++ ) + { + /* with > 1 surface per raw lightmap, clear occluded */ + if ( n > 0 ) { + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + if ( *cluster < 0 ) { + *cluster = CLUSTER_UNMAPPED; + } + } + } + } + + /* get surface */ + num = lightSurfaces[ lm->firstLightSurface + n ]; + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* bail if no lightmap to calculate */ + if ( info->lm != lm ) { + Sys_Printf( "!" ); + continue; + } + + /* map the surface onto the lightmap origin/cluster/normal buffers */ + switch ( ds->surfaceType ) + { + case MST_PLANAR: + /* get verts */ + verts = yDrawVerts + ds->firstVert; + + /* map the triangles */ + for ( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ ) + { + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ]; + dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ]; + dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + } + } + break; + + case MST_PATCH: + case MST_PATCHFIXED: + /* make a mesh from the drawsurf */ + if (ds->surfaceType == MST_PATCHFIXED) + { + src.width = ds->patchWidth&0xffff; + src.height = ds->patchHeight&0xffff; + src.subdiv_x = ds->patchWidth>>16; + src.subdiv_y = ds->patchHeight>>16; + } + else + { + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.subdiv_x = src.subdiv_y = -1; + } + src.verts = &yDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* get verts */ + verts = mesh->verts; + + /* debug code */ + #if 0 + if ( lm->plane ) { + Sys_Printf( "Planar patch: [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f]\n", + lm->plane[ 0 ], lm->plane[ 1 ], lm->plane[ 2 ], + lm->vecs[ 0 ][ 0 ], lm->vecs[ 0 ][ 1 ], lm->vecs[ 0 ][ 2 ], + lm->vecs[ 1 ][ 0 ], lm->vecs[ 1 ][ 1 ], lm->vecs[ 1 ][ 2 ] ); + } + #endif + + /* map the mesh quads */ + #if 0 + + for ( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ ) + { + for ( y = 0; y < ( mesh->height - 1 ); y++ ) + { + for ( x = 0; x < ( mesh->width - 1 ); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + ( y * mesh->width ); + pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); + pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); + pw[ 3 ] = x + 1 + ( y * mesh->width ); + pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( x + y ) & 1; + + /* get drawverts and map first triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &verts[ pw[ r + 2 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + + /* get drawverts and map second triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 2 ] ]; + dv[ 2 ] = &verts[ pw[ r + 3 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + } + } + } + + #else + + for ( y = 0; y < ( mesh->height - 1 ); y++ ) + { + for ( x = 0; x < ( mesh->width - 1 ); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + ( y * mesh->width ); + pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); + pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); + pw[ 3 ] = x + 1 + ( y * mesh->width ); + pw[ 4 ] = pw[ 0 ]; + + /* set radix */ + r = ( x + y ) & 1; + + /* attempt to map quad first */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &verts[ pw[ r + 2 ] ]; + dv[ 3 ] = &verts[ pw[ r + 3 ] ]; + if ( MapQuad( lm, info, dv ) ) { + continue; + } + + for ( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ ) + { + /* get drawverts and map first triangle */ + dv[ 1 ] = &verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &verts[ pw[ r + 2 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + + /* get drawverts and map second triangle */ + dv[ 1 ] = &verts[ pw[ r + 2 ] ]; + dv[ 2 ] = &verts[ pw[ r + 3 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + } + } + } + + #endif + + /* free the mesh */ + FreeMesh( mesh ); + break; + + default: + break; + } + } + + /* ----------------------------------------------------------------- + average and clean up luxel normals + ----------------------------------------------------------------- */ + + /* walk the luxels */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* only look at mapped luxels */ + if ( *cluster < 0 ) { + continue; + } + + /* the normal data could be the sum of multiple samples */ + if ( luxel[ 3 ] > 1.0f ) { + VectorNormalize( normal, normal ); + } + + /* mark this luxel as having only one normal */ + luxel[ 3 ] = 1.0f; + } + } + + /* non-planar surfaces stop here */ + if ( lm->plane == NULL ) { + return; + } + + /* ----------------------------------------------------------------- + map occluded or unuxed luxels + ----------------------------------------------------------------- */ + + /* walk the luxels */ + radius = floor( superSample / 2 ); + radius = radius > 0 ? radius : 1.0f; + radius += 1.0f; + for ( pass = 2.0f; pass <= radius; pass += 1.0f ) + { + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* only look at unmapped luxels */ + if ( *cluster != CLUSTER_UNMAPPED ) { + continue; + } + + /* divine a normal and origin from neighboring luxels */ + VectorClear( fake.xyz ); + VectorClear( fake.normal ); + fake.lightmap[ 0 ][ 0 ] = x; //% 0.0001 + x; + fake.lightmap[ 0 ][ 1 ] = y; //% 0.0001 + y; + samples = 0.0f; + for ( sy = ( y - 1 ); sy <= ( y + 1 ); sy++ ) + { + if ( sy < 0 || sy >= lm->sh ) { + continue; + } + + for ( sx = ( x - 1 ); sx <= ( x + 1 ); sx++ ) + { + if ( sx < 0 || sx >= lm->sw || ( sx == x && sy == y ) ) { + continue; + } + + /* get neighboring luxel */ + luxel = SUPER_LUXEL( 0, sx, sy ); + origin = SUPER_ORIGIN( sx, sy ); + normal = SUPER_NORMAL( sx, sy ); + cluster = SUPER_CLUSTER( sx, sy ); + + /* only consider luxels mapped in previous passes */ + if ( *cluster < 0 || luxel[ 0 ] >= pass ) { + continue; + } + + /* add its distinctiveness to our own */ + VectorAdd( fake.xyz, origin, fake.xyz ); + VectorAdd( fake.normal, normal, fake.normal ); + samples += luxel[ 3 ]; + } + } + + /* any samples? */ + if ( samples == 0.0f ) { + continue; + } + + /* average */ + VectorDivide( fake.xyz, samples, fake.xyz ); + //% VectorDivide( fake.normal, samples, fake.normal ); + if ( VectorNormalize( fake.normal, fake.normal ) == 0.0f ) { + continue; + } + + /* map the fake vert */ + MapSingleLuxel( lm, NULL, &fake, lm->plane, pass, NULL, NULL, NULL ); + } + } + } + + /* ----------------------------------------------------------------- + average and clean up luxel normals + ----------------------------------------------------------------- */ + + /* walk the luxels */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* only look at mapped luxels */ + if ( *cluster < 0 ) { + continue; + } + + /* the normal data could be the sum of multiple samples */ + if ( luxel[ 3 ] > 1.0f ) { + VectorNormalize( normal, normal ); + } + + /* mark this luxel as having only one normal */ + luxel[ 3 ] = 1.0f; + } + } + + /* debug code */ + #if 0 + Sys_Printf( "\n" ); + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + vec3_t mins, maxs; + + + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + luxel = SUPER_LUXEL( x, y ); + + if ( *cluster < 0 ) { + continue; + } + + /* check if within the bounding boxes of all surfaces referenced */ + ClearBounds( mins, maxs ); + for ( n = 0; n < lm->numLightSurfaces; n++ ) + { + int TOL; + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + n ] ]; + TOL = info->sampleSize + 2; + AddPointToBounds( info->mins, mins, maxs ); + AddPointToBounds( info->maxs, mins, maxs ); + if ( origin[ 0 ] > ( info->mins[ 0 ] - TOL ) && origin[ 0 ] < ( info->maxs[ 0 ] + TOL ) && + origin[ 1 ] > ( info->mins[ 1 ] - TOL ) && origin[ 1 ] < ( info->maxs[ 1 ] + TOL ) && + origin[ 2 ] > ( info->mins[ 2 ] - TOL ) && origin[ 2 ] < ( info->maxs[ 2 ] + TOL ) ) { + break; + } + } + + /* inside? */ + if ( n < lm->numLightSurfaces ) { + continue; + } + + /* report bogus origin */ + Sys_Printf( "%6d [%2d,%2d] (%4d): XYZ(%+4.1f %+4.1f %+4.1f) LO(%+4.1f %+4.1f %+4.1f) HI(%+4.1f %+4.1f %+4.1f) <%3.0f>\n", + rawLightmapNum, x, y, *cluster, + origin[ 0 ], origin[ 1 ], origin[ 2 ], + mins[ 0 ], mins[ 1 ], mins[ 2 ], + maxs[ 0 ], maxs[ 1 ], maxs[ 2 ], + luxel[ 3 ] ); + } + } + #endif +} + + + +/* + SetupDirt() + sets up dirtmap (ambient occlusion) + */ + +#define DIRT_CONE_ANGLE 88 /* degrees */ +#define DIRT_NUM_ANGLE_STEPS 16 +#define DIRT_NUM_ELEVATION_STEPS 3 +#define DIRT_NUM_VECTORS ( DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS ) + +static vec3_t dirtVectors[ DIRT_NUM_VECTORS ]; +static int numDirtVectors = 0; + +void SetupDirt( void ){ + int i, j; + float angle, elevation, angleStep, elevationStep; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupDirt ---\n" ); + + /* calculate angular steps */ + angleStep = DEG2RAD( 360.0f / DIRT_NUM_ANGLE_STEPS ); + elevationStep = DEG2RAD( DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS ); + + /* iterate angle */ + angle = 0.0f; + for ( i = 0, angle = 0.0f; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep ) + { + /* iterate elevation */ + for ( j = 0, elevation = elevationStep * 0.5f; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) + { + dirtVectors[ numDirtVectors ][ 0 ] = sin( elevation ) * cos( angle ); + dirtVectors[ numDirtVectors ][ 1 ] = sin( elevation ) * sin( angle ); + dirtVectors[ numDirtVectors ][ 2 ] = cos( elevation ); + numDirtVectors++; + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d dirtmap vectors\n", numDirtVectors ); +} + + +/* + DirtForSample() + calculates dirt value for a given sample + */ + +float DirtForSample( trace_t *trace ){ + int i; + float gatherDirt, outDirt, angle, elevation, ooDepth; + vec3_t normal, worldUp, myUp, myRt, temp, direction, displacement; + + + /* dummy check */ + if ( !dirty ) { + return 1.0f; + } + if ( trace == NULL || trace->cluster < 0 ) { + return 0.0f; + } + + /* setup */ + gatherDirt = 0.0f; + ooDepth = 1.0f / dirtDepth; + VectorCopy( trace->normal, normal ); + + /* check if the normal is aligned to the world-up */ + if ( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f && ( normal[ 2 ] == 1.0f || normal[ 2 ] == -1.0f ) ) { + if ( normal[ 2 ] == 1.0f ) { + VectorSet( myRt, 1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + else if ( normal[ 2 ] == -1.0f ) { + VectorSet( myRt, -1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + } + else + { + VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); + CrossProduct( normal, worldUp, myRt ); + VectorNormalize( myRt, myRt ); + CrossProduct( myRt, normal, myUp ); + VectorNormalize( myUp, myUp ); + } + + /* 1 = random mode, 0 (well everything else) = non-random mode */ + if ( dirtMode == 1 ) { + /* iterate */ + for ( i = 0; i < numDirtVectors; i++ ) + { + /* get random vector */ + angle = Random() * DEG2RAD( 360.0f ); + elevation = Random() * DEG2RAD( DIRT_CONE_ANGLE ); + temp[ 0 ] = cos( angle ) * sin( elevation ); + temp[ 1 ] = sin( angle ) * sin( elevation ); + temp[ 2 ] = cos( elevation ); + + /* transform into tangent space */ + direction[ 0 ] = myRt[ 0 ] * temp[ 0 ] + myUp[ 0 ] * temp[ 1 ] + normal[ 0 ] * temp[ 2 ]; + direction[ 1 ] = myRt[ 1 ] * temp[ 0 ] + myUp[ 1 ] * temp[ 1 ] + normal[ 1 ] * temp[ 2 ]; + direction[ 2 ] = myRt[ 2 ] * temp[ 0 ] + myUp[ 2 ] * temp[ 1 ] + normal[ 2 ] * temp[ 2 ]; + + /* set endpoint */ + VectorMA( trace->origin, dirtDepth, direction, trace->end ); + SetupTrace( trace ); + VectorSet(trace->color, 1.0f, 1.0f, 1.0f); + + /* trace */ + TraceLine( trace ); + if ( trace->opaque && !( trace->compileFlags & C_SKY ) ) { + VectorSubtract( trace->hit, trace->origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + } + } + else + { + /* iterate through ordered vectors */ + for ( i = 0; i < numDirtVectors; i++ ) + { + /* transform vector into tangent space */ + direction[ 0 ] = myRt[ 0 ] * dirtVectors[ i ][ 0 ] + myUp[ 0 ] * dirtVectors[ i ][ 1 ] + normal[ 0 ] * dirtVectors[ i ][ 2 ]; + direction[ 1 ] = myRt[ 1 ] * dirtVectors[ i ][ 0 ] + myUp[ 1 ] * dirtVectors[ i ][ 1 ] + normal[ 1 ] * dirtVectors[ i ][ 2 ]; + direction[ 2 ] = myRt[ 2 ] * dirtVectors[ i ][ 0 ] + myUp[ 2 ] * dirtVectors[ i ][ 1 ] + normal[ 2 ] * dirtVectors[ i ][ 2 ]; + + /* set endpoint */ + VectorMA( trace->origin, dirtDepth, direction, trace->end ); + SetupTrace( trace ); + VectorSet(trace->color, 1.0f, 1.0f, 1.0f); + + /* trace */ + TraceLine( trace ); + if ( trace->opaque ) { + VectorSubtract( trace->hit, trace->origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + } + } + + /* direct ray */ + VectorMA( trace->origin, dirtDepth, normal, trace->end ); + SetupTrace( trace ); + VectorSet(trace->color, 1.0f, 1.0f, 1.0f); + + /* trace */ + TraceLine( trace ); + if ( trace->opaque ) { + VectorSubtract( trace->hit, trace->origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + + /* early out */ + if ( gatherDirt <= 0.0f ) { + return 1.0f; + } + + /* apply gain (does this even do much? heh) */ + outDirt = pow( gatherDirt / ( numDirtVectors + 1 ), dirtGain ); + if ( outDirt > 1.0f ) { + outDirt = 1.0f; + } + + /* apply scale */ + outDirt *= dirtScale; + if ( outDirt > 1.0f ) { + outDirt = 1.0f; + } + + /* return to sender */ + return 1.0f - outDirt; +} + + + +/* + DirtyRawLightmap() + calculates dirty fraction for each luxel + */ + +void DirtyRawLightmap( int rawLightmapNum ){ + int i, x, y, sx, sy, *cluster; + float *origin, *normal, *dirt, *dirt2, average, samples; + rawLightmap_t *lm; + surfaceInfo_t *info; + trace_t trace; + qboolean noDirty; + + + /* bail if this number exceeds the number of raw lightmaps */ + if ( rawLightmapNum >= numRawLightmaps ) { + return; + } + + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + /* setup trace */ + trace.testOcclusion = qtrue; + trace.forceSunlight = qfalse; + trace.recvShadows = lm->recvShadows; + trace.numSurfaces = lm->numLightSurfaces; + trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; + trace.inhibitRadius = 0.0f; + trace.testAll = qfalse; + + /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ + trace.twoSided = qfalse; + for ( i = 0; i < trace.numSurfaces; i++ ) + { + /* get surface */ + info = &surfaceInfos[ trace.surfaces[ i ] ]; + + /* check twosidedness */ + if ( info->si->twoSided ) { + trace.twoSided = qtrue; + break; + } + } + + noDirty = qfalse; + for ( i = 0; i < trace.numSurfaces; i++ ) + { + /* get surface */ + info = &surfaceInfos[ trace.surfaces[ i ] ]; + + /* check twosidedness */ + if ( info->si->noDirty ) { + noDirty = qtrue; + break; + } + } + + /* gather dirt */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + dirt = SUPER_DIRT( x, y ); + + /* set default dirt */ + *dirt = 0.0f; + + /* only look at mapped luxels */ + if ( *cluster < 0 ) { + continue; + } + + /* don't apply dirty on this surface */ + if ( noDirty ) { + *dirt = 1.0f; + continue; + } + + /* copy to trace */ + trace.cluster = *cluster; + VectorCopy( origin, trace.origin ); + VectorCopy( normal, trace.normal ); + + /* get dirt */ + *dirt = DirtForSample( &trace ); + } + } + + /* testing no filtering */ + //% return; + + /* filter dirt */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + dirt = SUPER_DIRT( x, y ); + + /* filter dirt by adjacency to unmapped luxels */ + average = *dirt; + samples = 1.0f; + for ( sy = ( y - 1 ); sy <= ( y + 1 ); sy++ ) + { + if ( sy < 0 || sy >= lm->sh ) { + continue; + } + + for ( sx = ( x - 1 ); sx <= ( x + 1 ); sx++ ) + { + if ( sx < 0 || sx >= lm->sw || ( sx == x && sy == y ) ) { + continue; + } + + /* get neighboring luxel */ + cluster = SUPER_CLUSTER( sx, sy ); + dirt2 = SUPER_DIRT( sx, sy ); + if ( *cluster < 0 || *dirt2 <= 0.0f ) { + continue; + } + + /* add it */ + average += *dirt2; + samples += 1.0f; + } + + /* bail */ + if ( samples <= 0.0f ) { + break; + } + } + + /* bail */ + if ( samples <= 0.0f ) { + continue; + } + + /* scale dirt */ + *dirt = average / samples; + } + } +} + + + +/* + SubmapRawLuxel() + calculates the pvs cluster, origin, normal of a sub-luxel + */ + +static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float by, int *sampleCluster, vec3_t sampleOrigin, vec3_t sampleNormal ){ + int i, *cluster, *cluster2; + float *origin, *origin2, *normal; //% , *normal2; + vec3_t originVecs[ 2 ]; //% , normalVecs[ 2 ]; + + + /* calulate x vector */ + if ( ( x < ( lm->sw - 1 ) && bx >= 0.0f ) || ( x == 0 && bx <= 0.0f ) ) { + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + //% normal = SUPER_NORMAL( x, y ); + cluster2 = SUPER_CLUSTER( x + 1, y ); + origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x + 1, y ); + //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x + 1, y ); + } + else if ( ( x > 0 && bx <= 0.0f ) || ( x == ( lm->sw - 1 ) && bx >= 0.0f ) ) { + cluster = SUPER_CLUSTER( x - 1, y ); + origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x - 1, y ); + //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x - 1, y ); + cluster2 = SUPER_CLUSTER( x, y ); + origin2 = SUPER_ORIGIN( x, y ); + //% normal2 = SUPER_NORMAL( x, y ); + } + else { + Sys_FPrintf( SYS_WRN, "WARNING: Spurious lightmap S vector\n" ); + } + + VectorSubtract( origin2, origin, originVecs[ 0 ] ); + //% VectorSubtract( normal2, normal, normalVecs[ 0 ] ); + + /* calulate y vector */ + if ( ( y < ( lm->sh - 1 ) && bx >= 0.0f ) || ( y == 0 && bx <= 0.0f ) ) { + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + //% normal = SUPER_NORMAL( x, y ); + cluster2 = SUPER_CLUSTER( x, y + 1 ); + origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y + 1 ); + //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y + 1 ); + } + else if ( ( y > 0 && bx <= 0.0f ) || ( y == ( lm->sh - 1 ) && bx >= 0.0f ) ) { + cluster = SUPER_CLUSTER( x, y - 1 ); + origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y - 1 ); + //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y - 1 ); + cluster2 = SUPER_CLUSTER( x, y ); + origin2 = SUPER_ORIGIN( x, y ); + //% normal2 = SUPER_NORMAL( x, y ); + } + else { + Sys_FPrintf( SYS_WRN, "WARNING: Spurious lightmap T vector\n" ); + } + + VectorSubtract( origin2, origin, originVecs[ 1 ] ); + + /* calculate new origin */ + for ( i = 0; i < 3; i++ ) + sampleOrigin[ i ] = sampleOrigin[ i ] + ( bx * originVecs[ 0 ][ i ] ) + ( by * originVecs[ 1 ][ i ] ); + + /* get cluster */ + *sampleCluster = ClusterForPointExtFilter( sampleOrigin, ( LUXEL_EPSILON * 2 ), lm->numLightClusters, lm->lightClusters ); + if ( *sampleCluster < 0 ) { + return qfalse; + } + + /* calculate new normal */ + normal = SUPER_NORMAL( x, y ); + VectorCopy( normal, sampleNormal ); + + /* return ok */ + return qtrue; +} + + +/* + SubsampleRawLuxel_r() + recursively subsamples a luxel until its color gradient is low enough or subsampling limit is reached + */ + +static void SubsampleRawLuxel_r( rawLightmap_t *lm, trace_t *trace, vec3_t sampleOrigin, int x, int y, float bias, float *lightLuxel, float *lightDeluxel ){ + int b, samples, mapped, lighted; + int cluster[ 4 ]; + vec4_t luxel[ 4 ]; + vec3_t deluxel[ 3 ]; + vec3_t origin[ 4 ], normal[ 4 ]; + float biasDirs[ 4 ][ 2 ] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { -1.0f, 1.0f }, { 1.0f, 1.0f } }; + vec3_t color, direction = { 0, 0, 0 }, total; + + + /* limit check */ + if ( lightLuxel[ 3 ] >= lightSamples ) { + return; + } + + /* setup */ + VectorClear( total ); + mapped = 0; + lighted = 0; + + /* make 2x2 subsample stamp */ + for ( b = 0; b < 4; b++ ) + { + /* set origin */ + VectorCopy( sampleOrigin, origin[ b ] ); + + /* calculate position */ + if ( !SubmapRawLuxel( lm, x, y, ( bias * biasDirs[ b ][ 0 ] ), ( bias * biasDirs[ b ][ 1 ] ), &cluster[ b ], origin[ b ], normal[ b ] ) ) { + cluster[ b ] = -1; + continue; + } + mapped++; + + /* increment sample count */ + luxel[ b ][ 3 ] = lightLuxel[ 3 ] + 1.0f; + + /* setup trace */ + trace->cluster = *cluster; + VectorCopy( origin[ b ], trace->origin ); + VectorCopy( normal[ b ], trace->normal ); + + /* sample light */ + + LightContributionToSample( trace ); + if ( trace->forceSubsampling > 1.0f ) { + /* alphashadow: we subsample as deep as we can */ + ++lighted; + ++mapped; + ++mapped; + } + + /* add to totals (fixme: make contrast function) */ + VectorCopy( trace->color, luxel[ b ] ); + if ( lightDeluxel ) { + VectorCopy( trace->directionContribution, deluxel[ b ] ); + } + VectorAdd( total, trace->color, total ); + if ( ( luxel[ b ][ 0 ] + luxel[ b ][ 1 ] + luxel[ b ][ 2 ] ) > 0.0f ) { + lighted++; + } + } + + /* subsample further? */ + if ( ( lightLuxel[ 3 ] + 1.0f ) < lightSamples && + ( total[ 0 ] > 4.0f || total[ 1 ] > 4.0f || total[ 2 ] > 4.0f ) && + lighted != 0 && lighted != mapped ) { + for ( b = 0; b < 4; b++ ) + { + if ( cluster[ b ] < 0 ) { + continue; + } + SubsampleRawLuxel_r( lm, trace, origin[ b ], x, y, ( bias * 0.5f ), luxel[ b ], lightDeluxel ? deluxel[ b ] : NULL ); + } + } + + /* average */ + //% VectorClear( color ); + //% samples = 0; + VectorCopy( lightLuxel, color ); + if ( lightDeluxel ) { + VectorCopy( lightDeluxel, direction ); + } + samples = 1; + for ( b = 0; b < 4; b++ ) + { + if ( cluster[ b ] < 0 ) { + continue; + } + VectorAdd( color, luxel[ b ], color ); + if ( lightDeluxel ) { + VectorAdd( direction, deluxel[ b ], direction ); + } + samples++; + } + + /* add to luxel */ + if ( samples > 0 ) { + /* average */ + color[ 0 ] /= samples; + color[ 1 ] /= samples; + color[ 2 ] /= samples; + + /* add to color */ + VectorCopy( color, lightLuxel ); + lightLuxel[ 3 ] += 1.0f; + + if ( lightDeluxel ) { + direction[ 0 ] /= samples; + direction[ 1 ] /= samples; + direction[ 2 ] /= samples; + VectorCopy( direction, lightDeluxel ); + } + } +} + +/* A mostly Gaussian-like bounded random distribution (sigma is expected standard deviation) */ +static void GaussLikeRandom( float sigma, float *x, float *y ){ + float r; + r = Random() * 2 * Q_PI; + *x = sigma * 2.73861278752581783822 * cos( r ); + *y = sigma * 2.73861278752581783822 * sin( r ); + r = Random(); + r = 1 - sqrt( r ); + r = 1 - sqrt( r ); + *x *= r; + *y *= r; +} +static void RandomSubsampleRawLuxel( rawLightmap_t *lm, trace_t *trace, vec3_t sampleOrigin, int x, int y, float bias, float *lightLuxel, float *lightDeluxel ){ + int b, mapped; + int cluster; + vec3_t origin, normal; + vec3_t total, totaldirection; + float dx, dy; + + VectorClear( total ); + VectorClear( totaldirection ); + mapped = 0; + for ( b = 0; b < lightSamples; ++b ) + { + /* set origin */ + VectorCopy( sampleOrigin, origin ); + GaussLikeRandom( bias, &dx, &dy ); + + /* calculate position */ + if ( !SubmapRawLuxel( lm, x, y, dx, dy, &cluster, origin, normal ) ) { + cluster = -1; + continue; + } + mapped++; + + trace->cluster = cluster; + VectorCopy( origin, trace->origin ); + VectorCopy( normal, trace->normal ); + + LightContributionToSample( trace ); + VectorAdd( total, trace->color, total ); + if ( lightDeluxel ) { + VectorAdd( totaldirection, trace->directionContribution, totaldirection ); + } + } + + /* add to luxel */ + if ( mapped > 0 ) { + /* average */ + lightLuxel[ 0 ] = total[ 0 ] / mapped; + lightLuxel[ 1 ] = total[ 1 ] / mapped; + lightLuxel[ 2 ] = total[ 2 ] / mapped; + + if ( lightDeluxel ) { + lightDeluxel[ 0 ] = totaldirection[ 0 ] / mapped; + lightDeluxel[ 1 ] = totaldirection[ 1 ] / mapped; + lightDeluxel[ 2 ] = totaldirection[ 2 ] / mapped; + } + } +} + + + +/* + IlluminateRawLightmap() + illuminates the luxels + */ + +#define STACK_LL_SIZE ( SUPER_LUXEL_SIZE * 64 * 64 ) +#define LIGHT_LUXEL( x, y ) ( lightLuxels + ( ( ( ( y ) * lm->sw ) + ( x ) ) * SUPER_LUXEL_SIZE ) ) +#define LIGHT_DELUXEL( x, y ) ( lightDeluxels + ( ( ( ( y ) * lm->sw ) + ( x ) ) * SUPER_DELUXEL_SIZE ) ) + +void IlluminateRawLightmap( int rawLightmapNum ){ + int i, t, x, y, sx, sy, size, luxelFilterRadius, lightmapNum; + int *cluster, *cluster2, mapped, lighted, totalLighted; + size_t llSize, ldSize; + rawLightmap_t *lm; + surfaceInfo_t *info; + qboolean filterColor, filterDir; + float brightness; + float *origin, *normal, *dirt, *luxel, *luxel2, *deluxel, *deluxel2; + unsigned char *flag; + float *lightLuxels, *lightDeluxels, *lightLuxel, *lightDeluxel, samples, filterRadius, weight; + vec3_t color, direction, averageColor, averageDir, total, temp, temp2; + float tests[ 4 ][ 2 ] = { { 0.0f, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; + trace_t trace; + float stackLightLuxels[ STACK_LL_SIZE ]; + + + /* bail if this number exceeds the number of raw lightmaps */ + if ( rawLightmapNum >= numRawLightmaps ) { + return; + } + + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + /* setup trace */ + trace.testOcclusion = !noTrace; + trace.forceSunlight = qfalse; + trace.recvShadows = lm->recvShadows; + trace.numSurfaces = lm->numLightSurfaces; + trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + + /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ + trace.twoSided = qfalse; + for ( i = 0; i < trace.numSurfaces; i++ ) + { + /* get surface */ + info = &surfaceInfos[ trace.surfaces[ i ] ]; + + /* check twosidedness */ + if ( info->si->twoSided ) { + trace.twoSided = qtrue; + break; + } + } + + /* create a culled light list for this raw lightmap */ + CreateTraceLightsForBounds( lm->mins, lm->maxs, lm->plane, lm->numLightClusters, lm->lightClusters, LIGHT_SURFACES, &trace ); + + /* ----------------------------------------------------------------- + fill pass + ----------------------------------------------------------------- */ + + /* set counts */ + numLuxelsIlluminated += ( lm->sw * lm->sh ); + + /* test debugging state */ + if ( debugSurfaces || debugAxis || debugCluster || debugOrigin || dirtDebug || normalmap ) { + /* debug fill the luxels */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + + /* only fill mapped luxels */ + if ( *cluster < 0 ) { + continue; + } + + /* get particulars */ + luxel = SUPER_LUXEL( 0, x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + + /* color the luxel with raw lightmap num? */ + if ( debugSurfaces ) { + VectorCopy( debugColors[ rawLightmapNum % 12 ], luxel ); + } + + /* color the luxel with lightmap axis? */ + else if ( debugAxis ) { + luxel[ 0 ] = ( lm->axis[ 0 ] + 1.0f ) * 127.5f; + luxel[ 1 ] = ( lm->axis[ 1 ] + 1.0f ) * 127.5f; + luxel[ 2 ] = ( lm->axis[ 2 ] + 1.0f ) * 127.5f; + } + + /* color the luxel with luxel cluster? */ + else if ( debugCluster ) { + VectorCopy( debugColors[ *cluster % 12 ], luxel ); + } + + /* color the luxel with luxel origin? */ + else if ( debugOrigin ) { + VectorSubtract( lm->maxs, lm->mins, temp ); + VectorScale( temp, ( 1.0f / 255.0f ), temp ); + VectorSubtract( origin, lm->mins, temp2 ); + luxel[ 0 ] = lm->mins[ 0 ] + ( temp[ 0 ] * temp2[ 0 ] ); + luxel[ 1 ] = lm->mins[ 1 ] + ( temp[ 1 ] * temp2[ 1 ] ); + luxel[ 2 ] = lm->mins[ 2 ] + ( temp[ 2 ] * temp2[ 2 ] ); + } + + /* color the luxel with the normal */ + else if ( normalmap ) { + luxel[ 0 ] = ( normal[ 0 ] + 1.0f ) * 127.5f; + luxel[ 1 ] = ( normal[ 1 ] + 1.0f ) * 127.5f; + luxel[ 2 ] = ( normal[ 2 ] + 1.0f ) * 127.5f; + } + + /* otherwise clear it */ + else{ + VectorClear( luxel ); + } + + /* add to counts */ + luxel[ 3 ] = 1.0f; + } + } + } + else + { + /* allocate temporary per-light luxel storage */ + llSize = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + ldSize = lm->sw * lm->sh * SUPER_DELUXEL_SIZE * sizeof( float ); + if ( llSize <= ( STACK_LL_SIZE * sizeof( float ) ) ) { + lightLuxels = stackLightLuxels; + } + else{ + lightLuxels = safe_malloc( llSize ); + } + if ( deluxemap ) { + lightDeluxels = safe_malloc( ldSize ); + } + else{ + lightDeluxels = NULL; + } + + /* clear luxels */ + //% memset( lm->superLuxels[ 0 ], 0, llSize ); + + /* set ambient color */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* blacken unmapped clusters */ + if ( *cluster < 0 ) { + VectorClear( luxel ); + } + + /* set ambient */ + else + { + VectorCopy( ambientColor, luxel ); + if ( deluxemap ) { + brightness = RGBTOGRAY( ambientColor ) * ( 1.0f / 255.0f ); + + // use AT LEAST this amount of contribution from ambient for the deluxemap, fixes points that receive ZERO light + if ( brightness < 0.00390625f ) { + brightness = 0.00390625f; + } + + VectorScale( normal, brightness, deluxel ); + } + luxel[ 3 ] = 1.0f; + } + } + } + + /* clear styled lightmaps */ + size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + for ( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + if ( lm->superLuxels[ lightmapNum ] != NULL ) { + memset( lm->superLuxels[ lightmapNum ], 0, size ); + } + } + + /* debugging code */ + //% if( trace.numLights <= 0 ) + //% Sys_Printf( "Lightmap %9d: 0 lights, axis: %.2f, %.2f, %.2f\n", rawLightmapNum, lm->axis[ 0 ], lm->axis[ 1 ], lm->axis[ 2 ] ); + + /* walk light list */ + for ( i = 0; i < trace.numLights; i++ ) + { + /* setup trace */ + trace.light = trace.lights[ i ]; + + /* style check */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + if ( lm->styles[ lightmapNum ] == trace.light->style || + lm->styles[ lightmapNum ] == LS_NONE ) { + break; + } + } + + /* max of MAX_LIGHTMAPS (4) styles allowed to hit a surface/lightmap */ + if ( lightmapNum >= MAX_LIGHTMAPS ) { + Sys_FPrintf( SYS_WRN, "WARNING: Hit per-surface style limit (%d)\n", MAX_LIGHTMAPS ); + continue; + } + + /* setup */ + memset( lightLuxels, 0, llSize ); + if ( deluxemap ) { + memset( lightDeluxels, 0, ldSize ); + } + totalLighted = 0; + + /* determine filter radius */ + filterRadius = lm->filterRadius > trace.light->filterRadius + ? lm->filterRadius + : trace.light->filterRadius; + if ( filterRadius < 0.0f ) { + filterRadius = 0.0f; + } + + /* set luxel filter radius */ + luxelFilterRadius = lm->sampleSize != 0 ? superSample * filterRadius / lm->sampleSize : 0; + if ( luxelFilterRadius == 0 && ( filterRadius > 0.0f || filter ) ) { + luxelFilterRadius = 1; + } + + /* allocate sampling flags storage */ + if ( ( lightSamples > 1 || lightRandomSamples ) && luxelFilterRadius == 0 ) { + size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( unsigned char ); + if ( lm->superFlags == NULL ) { + lm->superFlags = safe_malloc( size ); + } + memset( (void *) lm->superFlags, 0, size ); + } + + /* initial pass, one sample per luxel */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + if ( *cluster < 0 ) { + continue; + } + + /* get particulars */ + lightLuxel = LIGHT_LUXEL( x, y ); + lightDeluxel = LIGHT_DELUXEL( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + flag = SUPER_FLAG( x, y ); + + /* set contribution count */ + lightLuxel[ 3 ] = 1.0f; + + /* setup trace */ + trace.cluster = *cluster; + VectorCopy( origin, trace.origin ); + VectorCopy( normal, trace.normal ); + + /* get light for this sample */ + LightContributionToSample( &trace ); + VectorCopy( trace.color, lightLuxel ); + + /* add the contribution to the deluxemap */ + if ( deluxemap ) { + VectorCopy( trace.directionContribution, lightDeluxel ); + } + + /* check for evilness */ + if ( trace.forceSubsampling > 1.0f && ( lightSamples > 1 || lightRandomSamples ) && luxelFilterRadius == 0 ) { + totalLighted++; + *flag |= FLAG_FORCE_SUBSAMPLING; /* force */ + } + /* add to count */ + else if ( trace.color[ 0 ] || trace.color[ 1 ] || trace.color[ 2 ] ) { + totalLighted++; + } + } + } + + /* don't even bother with everything else if nothing was lit */ + if ( totalLighted == 0 ) { + continue; + } + + /* secondary pass, adaptive supersampling (fixme: use a contrast function to determine if subsampling is necessary) */ + /* 2003-09-27: changed it so filtering disamples supersampling, as it would waste time */ + if ( ( lightSamples > 1 || lightRandomSamples ) && luxelFilterRadius == 0 ) { + /* walk luxels */ + for ( y = 0; y < ( lm->sh - 1 ); y++ ) + { + for ( x = 0; x < ( lm->sw - 1 ); x++ ) + { + /* setup */ + mapped = 0; + lighted = 0; + VectorClear( total ); + + /* test 2x2 stamp */ + for ( t = 0; t < 4; t++ ) + { + /* set sample coords */ + sx = x + tests[ t ][ 0 ]; + sy = y + tests[ t ][ 1 ]; + + /* get cluster */ + cluster = SUPER_CLUSTER( sx, sy ); + if ( *cluster < 0 ) { + continue; + } + mapped++; + + /* get luxel */ + flag = SUPER_FLAG( sx, sy ); + if ( *flag & FLAG_FORCE_SUBSAMPLING ) { + /* force a lighted/mapped discrepancy so we subsample */ + ++lighted; + ++mapped; + ++mapped; + } + lightLuxel = LIGHT_LUXEL( sx, sy ); + VectorAdd( total, lightLuxel, total ); + if ( ( lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ] ) > 0.0f ) { + lighted++; + } + } + + /* if total color is under a certain amount, then don't bother subsampling */ + if ( total[ 0 ] <= 4.0f && total[ 1 ] <= 4.0f && total[ 2 ] <= 4.0f ) { + continue; + } + + /* if all 4 pixels are either in shadow or light, then don't subsample */ + if ( lighted != 0 && lighted != mapped ) { + for ( t = 0; t < 4; t++ ) + { + /* set sample coords */ + sx = x + tests[ t ][ 0 ]; + sy = y + tests[ t ][ 1 ]; + + /* get luxel */ + cluster = SUPER_CLUSTER( sx, sy ); + if ( *cluster < 0 ) { + continue; + } + flag = SUPER_FLAG( sx, sy ); + if ( *flag & FLAG_ALREADY_SUBSAMPLED ) { // already subsampled + continue; + } + lightLuxel = LIGHT_LUXEL( sx, sy ); + lightDeluxel = LIGHT_DELUXEL( sx, sy ); + origin = SUPER_ORIGIN( sx, sy ); + + /* only subsample shadowed luxels */ + //% if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) <= 0.0f ) + //% continue; + + /* subsample it */ + if ( lightRandomSamples ) { + RandomSubsampleRawLuxel( lm, &trace, origin, sx, sy, 0.5f * lightSamplesSearchBoxSize, lightLuxel, deluxemap ? lightDeluxel : NULL ); + } + else{ + SubsampleRawLuxel_r( lm, &trace, origin, sx, sy, 0.25f * lightSamplesSearchBoxSize, lightLuxel, deluxemap ? lightDeluxel : NULL ); + } + + *flag |= FLAG_ALREADY_SUBSAMPLED; + + /* debug code to colorize subsampled areas to yellow */ + //% luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + //% VectorSet( luxel, 255, 204, 0 ); + } + } + } + } + } + + /* tertiary pass, apply dirt map (ambient occlusion) */ + if ( 0 && dirty ) { + /* walk luxels */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + if ( *cluster < 0 ) { + continue; + } + + /* get particulars */ + lightLuxel = LIGHT_LUXEL( x, y ); + dirt = SUPER_DIRT( x, y ); + + /* scale light value */ + VectorScale( lightLuxel, *dirt, lightLuxel ); + } + } + } + + /* allocate sampling lightmap storage */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + /* allocate sampling lightmap storage */ + size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + lm->superLuxels[ lightmapNum ] = safe_malloc( size ); + memset( lm->superLuxels[ lightmapNum ], 0, size ); + } + + /* set style */ + if ( lightmapNum > 0 ) { + lm->styles[ lightmapNum ] = trace.light->style; + //% Sys_Printf( "Surface %6d has lightstyle %d\n", rawLightmapNum, trace.light->style ); + } + + /* copy to permanent luxels */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster and origin */ + cluster = SUPER_CLUSTER( x, y ); + if ( *cluster < 0 ) { + continue; + } + origin = SUPER_ORIGIN( x, y ); + + /* filter? */ + if ( luxelFilterRadius ) { + /* setup */ + VectorClear( averageColor ); + VectorClear( averageDir ); + samples = 0.0f; + + /* cheaper distance-based filtering */ + for ( sy = ( y - luxelFilterRadius ); sy <= ( y + luxelFilterRadius ); sy++ ) + { + if ( sy < 0 || sy >= lm->sh ) { + continue; + } + + for ( sx = ( x - luxelFilterRadius ); sx <= ( x + luxelFilterRadius ); sx++ ) + { + if ( sx < 0 || sx >= lm->sw ) { + continue; + } + + /* get particulars */ + cluster = SUPER_CLUSTER( sx, sy ); + if ( *cluster < 0 ) { + continue; + } + lightLuxel = LIGHT_LUXEL( sx, sy ); + lightDeluxel = LIGHT_DELUXEL( sx, sy ); + + /* create weight */ + weight = ( abs( sx - x ) == luxelFilterRadius ? 0.5f : 1.0f ); + weight *= ( abs( sy - y ) == luxelFilterRadius ? 0.5f : 1.0f ); + + /* scale luxel by filter weight */ + VectorScale( lightLuxel, weight, color ); + VectorAdd( averageColor, color, averageColor ); + if ( deluxemap ) { + VectorScale( lightDeluxel, weight, direction ); + VectorAdd( averageDir, direction, averageDir ); + } + samples += weight; + } + } + + /* any samples? */ + if ( samples <= 0.0f ) { + continue; + } + + /* scale into luxel */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + luxel[ 3 ] = 1.0f; + + /* handle negative light */ + if ( trace.light->flags & LIGHT_NEGATIVE ) { + luxel[ 0 ] -= averageColor[ 0 ] / samples; + luxel[ 1 ] -= averageColor[ 1 ] / samples; + luxel[ 2 ] -= averageColor[ 2 ] / samples; + } + + /* handle normal light */ + else + { + luxel[ 0 ] += averageColor[ 0 ] / samples; + luxel[ 1 ] += averageColor[ 1 ] / samples; + luxel[ 2 ] += averageColor[ 2 ] / samples; + } + + if ( deluxemap ) { + /* scale into luxel */ + deluxel = SUPER_DELUXEL( x, y ); + deluxel[ 0 ] += averageDir[ 0 ] / samples; + deluxel[ 1 ] += averageDir[ 1 ] / samples; + deluxel[ 2 ] += averageDir[ 2 ] / samples; + } + } + + /* single sample */ + else + { + /* get particulars */ + lightLuxel = LIGHT_LUXEL( x, y ); + lightDeluxel = LIGHT_DELUXEL( x, y ); + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* handle negative light */ + if ( trace.light->flags & LIGHT_NEGATIVE ) { + VectorScale( averageColor, -1.0f, averageColor ); + } + + /* add color */ + luxel[ 3 ] = 1.0f; + + /* handle negative light */ + if ( trace.light->flags & LIGHT_NEGATIVE ) { + VectorSubtract( luxel, lightLuxel, luxel ); + } + + /* handle normal light */ + else{ + VectorAdd( luxel, lightLuxel, luxel ); + } + + if ( deluxemap ) { + VectorAdd( deluxel, lightDeluxel, deluxel ); + } + } + } + } + } + + /* free temporary luxels */ + if ( lightLuxels != stackLightLuxels ) { + free( lightLuxels ); + } + + if ( deluxemap ) { + free( lightDeluxels ); + } + } + + /* free light list */ + FreeTraceLights( &trace ); + + /* floodlight pass */ + if ( floodlighty ) { + FloodlightIlluminateLightmap( lm ); + } + + if ( debugnormals ) { + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + continue; + } + + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + //% if( *cluster < 0 ) + //% continue; + + /* get particulars */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + normal = SUPER_NORMAL( x, y ); + + luxel[0] = ( normal[0] * 127 ) + 127; + luxel[1] = ( normal[1] * 127 ) + 127; + luxel[2] = ( normal[2] * 127 ) + 127; + } + } + } + } + + /* ----------------------------------------------------------------- + dirt pass + ----------------------------------------------------------------- */ + + if ( dirty ) { + /* walk lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + continue; + } + + /* apply dirt to each luxel */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + + /* get particulars */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + dirt = SUPER_DIRT( x, y ); + + /* apply dirt */ + VectorScale( luxel, *dirt, luxel ); + + /* debugging */ + if ( dirtDebug ) { + VectorSet( luxel, *dirt * 255.0f, *dirt * 255.0f, *dirt * 255.0f ); + } + } + } + } + } + + /* ----------------------------------------------------------------- + filter pass + ----------------------------------------------------------------- */ + + /* walk lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + continue; + } + + /* average occluded luxels from neighbors */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get particulars */ + cluster = SUPER_CLUSTER( x, y ); + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + normal = SUPER_NORMAL( x, y ); + + /* determine if filtering is necessary */ + filterColor = qfalse; + filterDir = qfalse; + if ( *cluster < 0 || + ( lm->splotchFix && ( luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ] ) ) ) { + filterColor = qtrue; + } + + if ( deluxemap && lightmapNum == 0 && ( *cluster < 0 || filter ) ) { + filterDir = qtrue; + } + + if ( !filterColor && !filterDir ) { + continue; + } + + /* choose seed amount */ + VectorClear( averageColor ); + VectorClear( averageDir ); + samples = 0.0f; + + /* walk 3x3 matrix */ + for ( sy = ( y - 1 ); sy <= ( y + 1 ); sy++ ) + { + if ( sy < 0 || sy >= lm->sh ) { + continue; + } + + for ( sx = ( x - 1 ); sx <= ( x + 1 ); sx++ ) + { + if ( sx < 0 || sx >= lm->sw || ( sx == x && sy == y ) ) { + continue; + } + + /* get neighbor's particulars */ + cluster2 = SUPER_CLUSTER( sx, sy ); + luxel2 = SUPER_LUXEL( lightmapNum, sx, sy ); + deluxel2 = SUPER_DELUXEL( sx, sy ); + + /* ignore unmapped/unlit luxels */ + if ( *cluster2 < 0 || luxel2[ 3 ] == 0.0f || + ( lm->splotchFix && VectorCompare( luxel2, ambientColor ) ) ) { + continue; + } + + /* add its distinctiveness to our own */ + VectorAdd( averageColor, luxel2, averageColor ); + samples += luxel2[ 3 ]; + if ( filterDir ) { + VectorAdd( averageDir, deluxel2, averageDir ); + } + } + } + + /* fall through */ + if ( samples <= 0.0f ) { + continue; + } + + /* dark lightmap seams */ + if ( dark ) { + if ( lightmapNum == 0 ) { + VectorMA( averageColor, 2.0f, ambientColor, averageColor ); + } + samples += 2.0f; + } + + /* average it */ + if ( filterColor ) { + VectorDivide( averageColor, samples, luxel ); + luxel[ 3 ] = 1.0f; + } + if ( filterDir ) { + VectorDivide( averageDir, samples, deluxel ); + } + + /* set cluster to -3 */ + if ( *cluster < 0 ) { + *cluster = CLUSTER_FLOODED; + } + } + } + } +} + +#ifdef VERTEXLIGHT + +/* + IlluminateVertexes() + light the surface vertexes + */ + +#define VERTEX_NUDGE 4.0f + +void IlluminateVertexes( int num ){ + int i, x, y, z, x1, y1, z1, sx, sy, radius, maxRadius, *cluster; + int lightmapNum, numAvg; + float samples, *vertLuxel, *radVertLuxel, *luxel, dirt; + vec3_t origin, temp, temp2, colors[ MAX_LIGHTMAPS ], avgColors[ MAX_LIGHTMAPS ]; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + rawLightmap_t *lm; + bspDrawVert_t *verts; + trace_t trace; + float floodLightAmount; + vec3_t floodColor; + + + /* get surface, info, and raw lightmap */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + lm = info->lm; + + /* ----------------------------------------------------------------- + illuminate the vertexes + ----------------------------------------------------------------- */ + + /* calculate vertex lighting for surfaces without lightmaps */ + if ( lm == NULL || cpmaHack ) { + /* setup trace */ + trace.testOcclusion = ( cpmaHack && lm != NULL ) ? qfalse : !noTrace; + trace.forceSunlight = info->si->forceSunlight; + trace.recvShadows = info->recvShadows; + trace.numSurfaces = 1; + trace.surfaces = # + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + + /* twosided lighting */ + trace.twoSided = info->si->twoSided; + + /* make light list for this surface */ + CreateTraceLightsForSurface( num, &trace ); + + /* setup */ + verts = yDrawVerts + ds->firstVert; + numAvg = 0; + memset( avgColors, 0, sizeof( avgColors ) ); + + /* walk the surface verts */ + for ( i = 0; i < ds->numVerts; i++ ) + { + /* get vertex luxel */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + + /* color the luxel with raw lightmap num? */ + if ( debugSurfaces ) { + VectorCopy( debugColors[ num % 12 ], radVertLuxel ); + } + + /* color the luxel with luxel origin? */ + else if ( debugOrigin ) { + VectorSubtract( info->maxs, info->mins, temp ); + VectorScale( temp, ( 1.0f / 255.0f ), temp ); + VectorSubtract( origin, lm->mins, temp2 ); + radVertLuxel[ 0 ] = info->mins[ 0 ] + ( temp[ 0 ] * temp2[ 0 ] ); + radVertLuxel[ 1 ] = info->mins[ 1 ] + ( temp[ 1 ] * temp2[ 1 ] ); + radVertLuxel[ 2 ] = info->mins[ 2 ] + ( temp[ 2 ] * temp2[ 2 ] ); + } + + /* color the luxel with the normal */ + else if ( normalmap ) { + radVertLuxel[ 0 ] = ( verts[ i ].normal[ 0 ] + 1.0f ) * 127.5f; + radVertLuxel[ 1 ] = ( verts[ i ].normal[ 1 ] + 1.0f ) * 127.5f; + radVertLuxel[ 2 ] = ( verts[ i ].normal[ 2 ] + 1.0f ) * 127.5f; + } + + /* illuminate the vertex */ + else + { + /* clear vertex luxel */ + VectorSet( radVertLuxel, -1.0f, -1.0f, -1.0f ); + + /* try at initial origin */ + trace.cluster = ClusterForPointExtFilter( verts[ i ].xyz, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] ); + if ( trace.cluster >= 0 ) { + /* setup trace */ + VectorCopy( verts[ i ].xyz, trace.origin ); + VectorCopy( verts[ i ].normal, trace.normal ); + + /* r7 dirt */ + if ( dirty && !bouncing ) { + dirt = DirtForSample( &trace ); + } + else{ + dirt = 1.0f; + } + + /* jal: floodlight */ + floodLightAmount = 0.0f; + VectorClear( floodColor ); + if ( floodlighty && !bouncing ) { + floodLightAmount = floodlightIntensity * FloodLightForSample( &trace, floodlightDistance, floodlight_lowquality ); + VectorScale( floodlightRGB, floodLightAmount, floodColor ); + } + + /* trace */ + LightingAtSample( &trace, ds->vertexStyles, colors ); + + /* store */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* r7 dirt */ + VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] ); + + /* jal: floodlight */ + VectorAdd( colors[ lightmapNum ], floodColor, colors[ lightmapNum ] ); + + /* store */ + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorCopy( colors[ lightmapNum ], radVertLuxel ); + VectorAdd( avgColors[ lightmapNum ], colors[ lightmapNum ], colors[ lightmapNum ] ); + } + } + + /* is this sample bright enough? */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + if ( radVertLuxel[ 0 ] <= ambientColor[ 0 ] && + radVertLuxel[ 1 ] <= ambientColor[ 1 ] && + radVertLuxel[ 2 ] <= ambientColor[ 2 ] ) { + /* nudge the sample point around a bit */ + for ( x = 0; x < 5; x++ ) + { + /* two's complement 0, 1, -1, 2, -2, etc */ + x1 = ( ( x >> 1 ) ^ ( x & 1 ? -1 : 0 ) ) + ( x & 1 ); + + for ( y = 0; y < 5; y++ ) + { + y1 = ( ( y >> 1 ) ^ ( y & 1 ? -1 : 0 ) ) + ( y & 1 ); + + for ( z = 0; z < 5; z++ ) + { + z1 = ( ( z >> 1 ) ^ ( z & 1 ? -1 : 0 ) ) + ( z & 1 ); + + /* nudge origin */ + trace.origin[ 0 ] = verts[ i ].xyz[ 0 ] + ( VERTEX_NUDGE * x1 ); + trace.origin[ 1 ] = verts[ i ].xyz[ 1 ] + ( VERTEX_NUDGE * y1 ); + trace.origin[ 2 ] = verts[ i ].xyz[ 2 ] + ( VERTEX_NUDGE * z1 ); + + /* try at nudged origin */ + trace.cluster = ClusterForPointExtFilter( origin, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] ); + if ( trace.cluster < 0 ) { + continue; + } + + /* r7 dirt */ + if ( dirty && !bouncing ) { + dirt = DirtForSample( &trace ); + } + else{ + dirt = 1.0f; + } + + /* jal: floodlight */ + floodLightAmount = 0.0f; + VectorClear( floodColor ); + if ( floodlighty && !bouncing ) { + floodLightAmount = floodlightIntensity * FloodLightForSample( &trace, floodlightDistance, floodlight_lowquality ); + VectorScale( floodlightRGB, floodLightAmount, floodColor ); + } + + /* trace */ + LightingAtSample( &trace, ds->vertexStyles, colors ); + + /* store */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* r7 dirt */ + VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] ); + + /* jal: floodlight */ + VectorAdd( colors[ lightmapNum ], floodColor, colors[ lightmapNum ] ); + + /* store */ + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorCopy( colors[ lightmapNum ], radVertLuxel ); + } + + /* bright enough? */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + if ( radVertLuxel[ 0 ] > ambientColor[ 0 ] || + radVertLuxel[ 1 ] > ambientColor[ 1 ] || + radVertLuxel[ 2 ] > ambientColor[ 2 ] ) { + x = y = z = 1000; + } + } + } + } + } + + /* add to average? */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + if ( radVertLuxel[ 0 ] > ambientColor[ 0 ] || + radVertLuxel[ 1 ] > ambientColor[ 1 ] || + radVertLuxel[ 2 ] > ambientColor[ 2 ] ) { + numAvg++; + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorAdd( avgColors[ lightmapNum ], radVertLuxel, avgColors[ lightmapNum ] ); + } + } + } + + /* another happy customer */ + numVertsIlluminated++; + } + + /* set average color */ + if ( numAvg > 0 ) { + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + VectorScale( avgColors[ lightmapNum ], ( 1.0f / numAvg ), avgColors[ lightmapNum ] ); + } + else + { + VectorCopy( ambientColor, avgColors[ 0 ] ); + } + + /* clean up and store vertex color */ + for ( i = 0; i < ds->numVerts; i++ ) + { + /* get vertex luxel */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + + /* store average in occluded vertexes */ + if ( radVertLuxel[ 0 ] < 0.0f ) { + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorCopy( avgColors[ lightmapNum ], radVertLuxel ); + + /* debug code */ + //% VectorSet( radVertLuxel, 255.0f, 0.0f, 0.0f ); + } + } + + /* store it */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* get luxels */ + vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + + /* store */ + if ( bouncing || bounce == 0 || !bounceOnly ) { + VectorAdd( vertLuxel, radVertLuxel, vertLuxel ); + } + if ( !info->si->noVertexLight ) { + ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], info->si->vertexScale ); + } + } + } + + /* free light list */ + FreeTraceLights( &trace ); + + /* return to sender */ + return; + } + + /* ----------------------------------------------------------------- + reconstitute vertex lighting from the luxels + ----------------------------------------------------------------- */ + + /* set styles from lightmap */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + ds->vertexStyles[ lightmapNum ] = lm->styles[ lightmapNum ]; + + /* get max search radius */ + maxRadius = lm->sw; + maxRadius = maxRadius > lm->sh ? maxRadius : lm->sh; + + /* walk the surface verts */ + verts = yDrawVerts + ds->firstVert; + for ( i = 0; i < ds->numVerts; i++ ) + { + /* do each lightmap */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + continue; + } + + /* get luxel coords */ + x = verts[ i ].lightmap[ lightmapNum ][ 0 ]; + y = verts[ i ].lightmap[ lightmapNum ][ 1 ]; + if ( x < 0 ) { + x = 0; + } + else if ( x >= lm->sw ) { + x = lm->sw - 1; + } + if ( y < 0 ) { + y = 0; + } + else if ( y >= lm->sh ) { + y = lm->sh - 1; + } + + /* get vertex luxels */ + vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + + /* color the luxel with the normal? */ + if ( normalmap ) { + radVertLuxel[ 0 ] = ( verts[ i ].normal[ 0 ] + 1.0f ) * 127.5f; + radVertLuxel[ 1 ] = ( verts[ i ].normal[ 1 ] + 1.0f ) * 127.5f; + radVertLuxel[ 2 ] = ( verts[ i ].normal[ 2 ] + 1.0f ) * 127.5f; + } + + /* color the luxel with surface num? */ + else if ( debugSurfaces ) { + VectorCopy( debugColors[ num % 12 ], radVertLuxel ); + } + + /* divine color from the superluxels */ + else + { + /* increasing radius */ + VectorClear( radVertLuxel ); + samples = 0.0f; + for ( radius = 0; radius < maxRadius && samples <= 0.0f; radius++ ) + { + /* sample within radius */ + for ( sy = ( y - radius ); sy <= ( y + radius ); sy++ ) + { + if ( sy < 0 || sy >= lm->sh ) { + continue; + } + + for ( sx = ( x - radius ); sx <= ( x + radius ); sx++ ) + { + if ( sx < 0 || sx >= lm->sw ) { + continue; + } + + /* get luxel particulars */ + luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + cluster = SUPER_CLUSTER( sx, sy ); + if ( *cluster < 0 ) { + continue; + } + + /* testing: must be brigher than ambient color */ + //% if( luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ] ) + //% continue; + + /* add its distinctiveness to our own */ + VectorAdd( radVertLuxel, luxel, radVertLuxel ); + samples += luxel[ 3 ]; + } + } + } + + /* any color? */ + if ( samples > 0.0f ) { + VectorDivide( radVertLuxel, samples, radVertLuxel ); + } + else{ + VectorCopy( ambientColor, radVertLuxel ); + } + } + + /* store into floating point storage */ + VectorAdd( vertLuxel, radVertLuxel, vertLuxel ); + numVertsIlluminated++; + + /* store into bytes (for vertex approximation) */ + if ( !info->si->noVertexLight ) { + ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], 1.0f ); + } + } + } +} +#endif + + +/* ------------------------------------------------------------------------------- + + light optimization (-fast) + + creates a list of lights that will affect a surface and stores it in tw + this is to optimize surface lighting by culling out as many of the + lights in the world as possible from further calculation + + ------------------------------------------------------------------------------- */ + +/* + SetupBrushes() + determines opaque brushes in the world and find sky shaders for sunlight calculations + */ + +void SetupBrushesFlags( unsigned int mask_any, unsigned int test_any, unsigned int mask_all, unsigned int test_all ){ + int i, j, b; + unsigned int compileFlags, allCompileFlags; + qboolean inside; + bspBrush_t *brush; + bspBrushSide_t *side; + bspShader_t *shader; + shaderInfo_t *si; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupBrushes ---\n" ); + + /* allocate */ + if ( opaqueBrushes == NULL ) { + opaqueBrushes = safe_malloc( numBSPBrushes / 8 + 1 ); + } + + /* clear */ + memset( opaqueBrushes, 0, numBSPBrushes / 8 + 1 ); + numOpaqueBrushes = 0; + + /* walk the list of worldspawn brushes */ + for ( i = 0; i < bspModels[ 0 ].numBSPBrushes; i++ ) + { + /* get brush */ + b = bspModels[ 0 ].firstBSPBrush + i; + brush = &bspBrushes[ b ]; + + /* check all sides */ + inside = qtrue; + compileFlags = 0; + allCompileFlags = ~( 0u ); + for ( j = 0; j < brush->numSides && inside; j++ ) + { + /* do bsp shader calculations */ + side = &bspBrushSides[ brush->firstSide + j ]; + shader = &bspShaders[ side->shaderNum ]; + + /* get shader info */ + si = ShaderInfoForShaderNull( shader->shader ); + if ( si == NULL ) { + continue; + } + + /* or together compile flags */ + compileFlags |= si->compileFlags; + allCompileFlags &= si->compileFlags; + } + + /* determine if this brush is opaque to light */ + if ( ( compileFlags & mask_any ) == test_any && ( allCompileFlags & mask_all ) == test_all ) { + opaqueBrushes[ b >> 3 ] |= ( 1 << ( b & 7 ) ); + numOpaqueBrushes++; + maxOpaqueBrush = i; + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d opaque brushes\n", numOpaqueBrushes ); +} +void SetupBrushes( void ){ + SetupBrushesFlags( C_TRANSLUCENT, 0, 0, 0 ); +} + + + +/* + ClusterVisible() + determines if two clusters are visible to each other using the PVS + */ + +qboolean ClusterVisible( int a, int b ){ + int leafBytes; + byte *pvs; + + + /* dummy check */ + if ( a < 0 || b < 0 ) { + return qfalse; + } + + /* early out */ + if ( a == b ) { + return qtrue; + } + + /* not vised? */ + if ( numBSPVisBytes <= 8 ) { + return qtrue; + } + + /* get pvs data */ + /* portalClusters = ((int *) bspVisBytes)[ 0 ]; */ + leafBytes = ( (int*) bspVisBytes )[ 1 ]; + pvs = bspVisBytes + VIS_HEADER_SIZE + ( a * leafBytes ); + + /* check */ + if ( ( pvs[ b >> 3 ] & ( 1 << ( b & 7 ) ) ) ) { + return qtrue; + } + return qfalse; +} + + + +/* + PointInLeafNum_r() + borrowed from vlight.c + */ + +int PointInLeafNum_r( vec3_t point, int nodenum ){ + int leafnum; + vec_t dist; + bspNode_t *node; + bspPlane_t *plane; + + + while ( nodenum >= 0 ) + { + node = &bspNodes[ nodenum ]; + plane = &bspPlanes[ node->planeNum ]; + dist = DotProduct( point, plane->normal ) - plane->dist; + if ( dist > 0.1 ) { + nodenum = node->children[ 0 ]; + } + else if ( dist < -0.1 ) { + nodenum = node->children[ 1 ]; + } + else + { + leafnum = PointInLeafNum_r( point, node->children[ 0 ] ); + if ( bspLeafs[ leafnum ].cluster != -1 ) { + return leafnum; + } + nodenum = node->children[ 1 ]; + } + } + + leafnum = -nodenum - 1; + return leafnum; +} + + + +/* + PointInLeafnum() + borrowed from vlight.c + */ + +int PointInLeafNum( vec3_t point ){ + return PointInLeafNum_r( point, 0 ); +} + + + +/* + ClusterVisibleToPoint() - ydnar + returns qtrue if point can "see" cluster + */ + +qboolean ClusterVisibleToPoint( vec3_t point, int cluster ){ + int pointCluster; + + + /* get leafNum for point */ + pointCluster = ClusterForPoint( point ); + if ( pointCluster < 0 ) { + return qfalse; + } + + /* check pvs */ + return ClusterVisible( pointCluster, cluster ); +} + + + +/* + ClusterForPoint() - ydnar + returns the pvs cluster for point + */ + +int ClusterForPoint( vec3_t point ){ + int leafNum; + + + /* get leafNum for point */ + leafNum = PointInLeafNum( point ); + if ( leafNum < 0 ) { + return -1; + } + + /* return the cluster */ + return bspLeafs[ leafNum ].cluster; +} + + + +/* + ClusterForPointExt() - ydnar + also takes brushes into account for occlusion testing + */ + +int ClusterForPointExt( vec3_t point, float epsilon ){ + int i, j, b, leafNum, cluster; + float dot; + qboolean inside; + int *brushes, numBSPBrushes; + bspLeaf_t *leaf; + bspBrush_t *brush; + bspPlane_t *plane; + + + /* get leaf for point */ + leafNum = PointInLeafNum( point ); + if ( leafNum < 0 ) { + return -1; + } + leaf = &bspLeafs[ leafNum ]; + + /* get the cluster */ + cluster = leaf->cluster; + if ( cluster < 0 ) { + return -1; + } + + /* transparent leaf, so check point against all brushes in the leaf */ + brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ]; + numBSPBrushes = leaf->numBSPLeafBrushes; + for ( i = 0; i < numBSPBrushes; i++ ) + { + /* get parts */ + b = brushes[ i ]; + if ( b > maxOpaqueBrush ) { + continue; + } + brush = &bspBrushes[ b ]; + if ( !( opaqueBrushes[ b >> 3 ] & ( 1 << ( b & 7 ) ) ) ) { + continue; + } + + /* check point against all planes */ + inside = qtrue; + for ( j = 0; j < brush->numSides && inside; j++ ) + { + plane = &bspPlanes[ bspBrushSides[ brush->firstSide + j ].planeNum ]; + dot = DotProduct( point, plane->normal ); + dot -= plane->dist; + if ( dot > epsilon ) { + inside = qfalse; + } + } + + /* if inside, return bogus cluster */ + if ( inside ) { + return -1 - b; + } + } + + /* if the point made it this far, it's not inside any opaque brushes */ + return cluster; +} + + + +/* + ClusterForPointExtFilter() - ydnar + adds cluster checking against a list of known valid clusters + */ + +int ClusterForPointExtFilter( vec3_t point, float epsilon, int numClusters, int *clusters ){ + int i, cluster; + + + /* get cluster for point */ + cluster = ClusterForPointExt( point, epsilon ); + + /* check if filtering is necessary */ + if ( cluster < 0 || numClusters <= 0 || clusters == NULL ) { + return cluster; + } + + /* filter */ + for ( i = 0; i < numClusters; i++ ) + { + if ( cluster == clusters[ i ] || ClusterVisible( cluster, clusters[ i ] ) ) { + return cluster; + } + } + + /* failed */ + return -1; +} + + + +/* + ShaderForPointInLeaf() - ydnar + checks a point against all brushes in a leaf, returning the shader of the brush + also sets the cumulative surface and content flags for the brush hit + */ + +int ShaderForPointInLeaf( vec3_t point, int leafNum, float epsilon, int wantContentFlags, int wantSurfaceFlags, int *contentFlags, int *surfaceFlags ){ + int i, j; + float dot; + qboolean inside; + int *brushes, numBSPBrushes; + bspLeaf_t *leaf; + bspBrush_t *brush; + bspBrushSide_t *side; + bspPlane_t *plane; + bspShader_t *shader; + int allSurfaceFlags, allContentFlags; + + + /* clear things out first */ + *surfaceFlags = 0; + *contentFlags = 0; + + /* get leaf */ + if ( leafNum < 0 ) { + return -1; + } + leaf = &bspLeafs[ leafNum ]; + + /* transparent leaf, so check point against all brushes in the leaf */ + brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ]; + numBSPBrushes = leaf->numBSPLeafBrushes; + for ( i = 0; i < numBSPBrushes; i++ ) + { + /* get parts */ + brush = &bspBrushes[ brushes[ i ] ]; + + /* check point against all planes */ + inside = qtrue; + allSurfaceFlags = 0; + allContentFlags = 0; + for ( j = 0; j < brush->numSides && inside; j++ ) + { + side = &bspBrushSides[ brush->firstSide + j ]; + plane = &bspPlanes[ side->planeNum ]; + dot = DotProduct( point, plane->normal ); + dot -= plane->dist; + if ( dot > epsilon ) { + inside = qfalse; + } + else + { + shader = &bspShaders[ side->shaderNum ]; + allSurfaceFlags |= shader->surfaceFlags; + allContentFlags |= shader->contentFlags; + } + } + + /* handle if inside */ + if ( inside ) { + /* if there are desired flags, check for same and continue if they aren't matched */ + if ( wantContentFlags && !( wantContentFlags & allContentFlags ) ) { + continue; + } + if ( wantSurfaceFlags && !( wantSurfaceFlags & allSurfaceFlags ) ) { + continue; + } + + /* store the cumulative flags and return the brush shader (which is mostly useless) */ + *surfaceFlags = allSurfaceFlags; + *contentFlags = allContentFlags; + return brush->shaderNum; + } + } + + /* if the point made it this far, it's not inside any brushes */ + return -1; +} + + + +/* + ChopBounds() + chops a bounding box by the plane defined by origin and normal + returns qfalse if the bounds is entirely clipped away + + this is not exactly the fastest way to do this... + */ + +qboolean ChopBounds( vec3_t mins, vec3_t maxs, vec3_t origin, vec3_t normal ){ + /* FIXME: rewrite this so it doesn't use bloody brushes */ + return qtrue; +} + + + +/* + SetupEnvelopes() + calculates each light's effective envelope, + taking into account brightness, type, and pvs. + */ + +#define LIGHT_EPSILON 0.125f +#define LIGHT_NUDGE 2.0f + +void SetupEnvelopes( qboolean forGrid, qboolean fastFlag ){ + int i, x, y, z, x1, y1, z1; + light_t *light, *light2, **owner; + bspLeaf_t *leaf; + vec3_t origin, dir, mins, maxs; + float radius, intensity; + light_t *buckets[ 256 ]; + + + /* early out for weird cases where there are no lights */ + if ( lights == NULL ) { + return; + } + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupEnvelopes%s ---\n", fastFlag ? " (fast)" : "" ); + + /* count lights */ + numLights = 0; + numCulledLights = 0; + owner = &lights; + while ( *owner != NULL ) + { + /* get light */ + light = *owner; + + /* handle negative lights */ + if ( light->photons < 0.0f || light->add < 0.0f ) { + light->photons *= -1.0f; + light->add *= -1.0f; + light->flags |= LIGHT_NEGATIVE; + } + + /* sunlight? */ + if ( light->type == EMIT_SUN ) { + /* special cased */ + light->cluster = 0; + light->envelope = MAX_WORLD_COORD * 8.0f; + VectorSet( light->mins, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f ); + VectorSet( light->maxs, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f ); + } + + /* everything else */ + else + { + /* get pvs cluster for light */ + light->cluster = ClusterForPointExt( light->origin, LIGHT_EPSILON ); + + /* invalid cluster? */ + if ( light->cluster < 0 ) { + /* nudge the sample point around a bit */ + for ( x = 0; x < 4; x++ ) + { + /* two's complement 0, 1, -1, 2, -2, etc */ + x1 = ( ( x >> 1 ) ^ ( x & 1 ? -1 : 0 ) ) + ( x & 1 ); + + for ( y = 0; y < 4; y++ ) + { + y1 = ( ( y >> 1 ) ^ ( y & 1 ? -1 : 0 ) ) + ( y & 1 ); + + for ( z = 0; z < 4; z++ ) + { + z1 = ( ( z >> 1 ) ^ ( z & 1 ? -1 : 0 ) ) + ( z & 1 ); + + /* nudge origin */ + origin[ 0 ] = light->origin[ 0 ] + ( LIGHT_NUDGE * x1 ); + origin[ 1 ] = light->origin[ 1 ] + ( LIGHT_NUDGE * y1 ); + origin[ 2 ] = light->origin[ 2 ] + ( LIGHT_NUDGE * z1 ); + + /* try at nudged origin */ + light->cluster = ClusterForPointExt( origin, LIGHT_EPSILON ); + if ( light->cluster < 0 ) { + continue; + } + + /* set origin */ + VectorCopy( origin, light->origin ); + } + } + } + } + + /* only calculate for lights in pvs and outside of opaque brushes */ + if ( light->cluster >= 0 ) { + /* set light fast flag */ + if ( fastFlag ) { + light->flags |= LIGHT_FAST_TEMP; + } + else{ + light->flags &= ~LIGHT_FAST_TEMP; + } + if ( fastpoint && ( light->type != EMIT_AREA ) ) { + light->flags |= LIGHT_FAST_TEMP; + } + if ( light->si && light->si->noFast ) { + light->flags &= ~( LIGHT_FAST | LIGHT_FAST_TEMP ); + } + + /* clear light envelope */ + light->envelope = 0; + + /* handle area lights */ + if ( exactPointToPolygon && light->type == EMIT_AREA && light->w != NULL ) { + light->envelope = MAX_WORLD_COORD * 8.0f; + + /* check for fast mode */ + if ( ( light->flags & LIGHT_FAST ) || ( light->flags & LIGHT_FAST_TEMP ) ) { + /* ugly hack to calculate extent for area lights, but only done once */ + VectorScale( light->normal, -1.0f, dir ); + for ( radius = 100.0f; radius < MAX_WORLD_COORD * 8.0f; radius += 10.0f ) + { + float factor; + + VectorMA( light->origin, radius, light->normal, origin ); + factor = PointToPolygonFormFactor( origin, dir, light->w ); + if ( factor < 0.0f ) { + factor *= -1.0f; + } + if ( ( factor * light->add ) <= light->falloffTolerance ) { + light->envelope = radius; + break; + } + } + } + + intensity = light->photons; /* hopefully not used */ + } + else + { + radius = 0.0f; + intensity = light->photons; + } + + /* other calcs */ + if ( light->envelope <= 0.0f ) { + /* solve distance for non-distance lights */ + if ( !( light->flags & LIGHT_ATTEN_DISTANCE ) ) { + light->envelope = MAX_WORLD_COORD * 8.0f; + } + + else if ( ( light->flags & LIGHT_FAST ) || ( light->flags & LIGHT_FAST_TEMP ) ) { + /* solve distance for linear lights */ + if ( ( light->flags & LIGHT_ATTEN_LINEAR ) ) { + light->envelope = ( ( intensity * linearScale ) - light->falloffTolerance ) / light->fade; + } + + /* + add = angle * light->photons * linearScale - (dist * light->fade); + T = (light->photons * linearScale) - (dist * light->fade); + T + (dist * light->fade) = (light->photons * linearScale); + dist * light->fade = (light->photons * linearScale) - T; + dist = ((light->photons * linearScale) - T) / light->fade; + */ + + /* solve for inverse square falloff */ + else{ + light->envelope = sqrt( intensity / light->falloffTolerance ) + radius; + } + + /* + add = light->photons / (dist * dist); + T = light->photons / (dist * dist); + T * (dist * dist) = light->photons; + dist = sqrt( light->photons / T ); + */ + } + else + { + /* solve distance for linear lights */ + if ( ( light->flags & LIGHT_ATTEN_LINEAR ) ) { + light->envelope = ( intensity * linearScale ) / light->fade; + } + + /* can't cull these */ + else{ + light->envelope = MAX_WORLD_COORD * 8.0f; + } + } + } + + /* chop radius against pvs */ + { + /* clear bounds */ + ClearBounds( mins, maxs ); + + /* check all leaves */ + for ( i = 0; i < numBSPLeafs; i++ ) + { + /* get test leaf */ + leaf = &bspLeafs[ i ]; + + /* in pvs? */ + if ( leaf->cluster < 0 ) { + continue; + } + if ( ClusterVisible( light->cluster, leaf->cluster ) == qfalse ) { /* ydnar: thanks Arnout for exposing my stupid error (this never failed before) */ + continue; + } + + /* add this leafs bbox to the bounds */ + VectorCopy( leaf->mins, origin ); + AddPointToBounds( origin, mins, maxs ); + VectorCopy( leaf->maxs, origin ); + AddPointToBounds( origin, mins, maxs ); + } + + /* test to see if bounds encompass light */ + for ( i = 0; i < 3; i++ ) + { + if ( mins[ i ] > light->origin[ i ] || maxs[ i ] < light->origin[ i ] ) { + //% Sys_FPrintf( SYS_WRN, "WARNING: Light PVS bounds (%.0f, %.0f, %.0f) -> (%.0f, %.0f, %.0f)\ndo not encompass light %d (%f, %f, %f)\n", + //% mins[ 0 ], mins[ 1 ], mins[ 2 ], + //% maxs[ 0 ], maxs[ 1 ], maxs[ 2 ], + //% numLights, light->origin[ 0 ], light->origin[ 1 ], light->origin[ 2 ] ); + AddPointToBounds( light->origin, mins, maxs ); + } + } + + /* chop the bounds by a plane for area lights and spotlights */ + if ( light->type == EMIT_AREA || light->type == EMIT_SPOT ) { + ChopBounds( mins, maxs, light->origin, light->normal ); + } + + /* copy bounds */ + VectorCopy( mins, light->mins ); + VectorCopy( maxs, light->maxs ); + + /* reflect bounds around light origin */ + //% VectorMA( light->origin, -1.0f, origin, origin ); + VectorScale( light->origin, 2, origin ); + VectorSubtract( origin, maxs, origin ); + AddPointToBounds( origin, mins, maxs ); + //% VectorMA( light->origin, -1.0f, mins, origin ); + VectorScale( light->origin, 2, origin ); + VectorSubtract( origin, mins, origin ); + AddPointToBounds( origin, mins, maxs ); + + /* calculate spherical bounds */ + VectorSubtract( maxs, light->origin, dir ); + radius = (float) VectorLength( dir ); + + /* if this radius is smaller than the envelope, then set the envelope to it */ + if ( radius < light->envelope ) { + light->envelope = radius; + //% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): culled\n", numLights ); + } + //% else + //% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): failed (%8.0f > %8.0f)\n", numLights, radius, light->envelope ); + } + + /* add grid/surface only check */ + if ( forGrid ) { + if ( !( light->flags & LIGHT_GRID ) ) { + light->envelope = 0.0f; + } + } + else + { + if ( !( light->flags & LIGHT_SURFACES ) ) { + light->envelope = 0.0f; + } + } + } + + /* culled? */ + if ( light->cluster < 0 || light->envelope <= 0.0f ) { + /* debug code */ + //% Sys_Printf( "Culling light: Cluster: %d Envelope: %f\n", light->cluster, light->envelope ); + + /* delete the light */ + numCulledLights++; + *owner = light->next; + if ( light->w != NULL ) { + free( light->w ); + } + free( light ); + continue; + } + } + + /* square envelope */ + light->envelope2 = ( light->envelope * light->envelope ); + + /* increment light count */ + numLights++; + + /* set next light */ + owner = &( ( **owner ).next ); + } + + /* bucket sort lights by style */ + memset( buckets, 0, sizeof( buckets ) ); + light2 = NULL; + for ( light = lights; light != NULL; light = light2 ) + { + /* get next light */ + light2 = light->next; + + /* filter into correct bucket */ + light->next = buckets[ light->style ]; + buckets[ light->style ] = light; + + /* if any styled light is present, automatically set nocollapse */ + if ( light->style != LS_NORMAL ) { + noCollapse = qtrue; + } + } + + /* filter back into light list */ + lights = NULL; + for ( i = 255; i >= 0; i-- ) + { + light2 = NULL; + for ( light = buckets[ i ]; light != NULL; light = light2 ) + { + light2 = light->next; + light->next = lights; + lights = light; + } + } + + /* emit some statistics */ + Sys_Printf( "%9d total lights\n", numLights ); + Sys_Printf( "%9d culled lights\n", numCulledLights ); +} + + + +/* + CreateTraceLightsForBounds() + creates a list of lights that affect the given bounding box and pvs clusters (bsp leaves) + */ + +void CreateTraceLightsForBounds( vec3_t mins, vec3_t maxs, vec3_t normal, int numClusters, int *clusters, int flags, trace_t *trace ){ + int i; + light_t *light; + vec3_t origin, dir, nullVector = { 0.0f, 0.0f, 0.0f }; + float radius, dist, length; + + + /* potential pre-setup */ + if ( numLights == 0 ) { + SetupEnvelopes( qfalse, fast ); + } + + /* debug code */ + //% Sys_Printf( "CTWLFB: (%4.1f %4.1f %4.1f) (%4.1f %4.1f %4.1f)\n", mins[ 0 ], mins[ 1 ], mins[ 2 ], maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + + /* allocate the light list */ + trace->lights = safe_malloc( sizeof( light_t* ) * ( numLights + 1 ) ); + trace->numLights = 0; + + /* calculate spherical bounds */ + VectorAdd( mins, maxs, origin ); + VectorScale( origin, 0.5f, origin ); + VectorSubtract( maxs, origin, dir ); + radius = (float) VectorLength( dir ); + + /* get length of normal vector */ + if ( normal != NULL ) { + length = VectorLength( normal ); + } + else + { + normal = nullVector; + length = 0; + } + + /* test each light and see if it reaches the sphere */ + /* note: the attenuation code MUST match LightingAtSample() */ + for ( light = lights; light; light = light->next ) + { + /* check zero sized envelope */ + if ( light->envelope <= 0 ) { + lightsEnvelopeCulled++; + continue; + } + + /* check flags */ + if ( !( light->flags & flags ) ) { + continue; + } + + /* sunlight skips all this nonsense */ + if ( light->type != EMIT_SUN ) { + /* sun only? */ + if ( sunOnly ) { + continue; + } + + /* check against pvs cluster */ + if ( numClusters > 0 && clusters != NULL ) { + for ( i = 0; i < numClusters; i++ ) + { + if ( ClusterVisible( light->cluster, clusters[ i ] ) ) { + break; + } + } + + /* fixme! */ + if ( i == numClusters ) { + lightsClusterCulled++; + continue; + } + } + + /* if the light's bounding sphere intersects with the bounding sphere then this light needs to be tested */ + VectorSubtract( light->origin, origin, dir ); + dist = VectorLength( dir ); + dist -= light->envelope; + dist -= radius; + if ( dist > 0 ) { + lightsEnvelopeCulled++; + continue; + } + + /* check bounding box against light's pvs envelope (note: this code never eliminated any lights, so disabling it) */ + #if 0 + skip = qfalse; + for ( i = 0; i < 3; i++ ) + { + if ( mins[ i ] > light->maxs[ i ] || maxs[ i ] < light->mins[ i ] ) { + skip = qtrue; + } + } + if ( skip ) { + lightsBoundsCulled++; + continue; + } + #endif + } + + /* planar surfaces (except twosided surfaces) have a couple more checks */ + if ( length > 0.0f && trace->twoSided == qfalse ) { + /* lights coplanar with a surface won't light it */ + if ( !( light->flags & LIGHT_TWOSIDED ) && DotProduct( light->normal, normal ) > 0.999f ) { + lightsPlaneCulled++; + continue; + } + + /* check to see if light is behind the plane */ + if ( DotProduct( light->origin, normal ) - DotProduct( origin, normal ) < -1.0f ) { + lightsPlaneCulled++; + continue; + } + } + + /* add this light */ + trace->lights[ trace->numLights++ ] = light; + } + + /* make last night null */ + trace->lights[ trace->numLights ] = NULL; +} + + + +void FreeTraceLights( trace_t *trace ){ + if ( trace->lights != NULL ) { + free( trace->lights ); + } +} + + + +/* + CreateTraceLightsForSurface() + creates a list of lights that can potentially affect a drawsurface + */ + +void CreateTraceLightsForSurface( int num, trace_t *trace ){ + int i; + vec3_t mins, maxs, normal; + bspDrawVert_t *dv; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + + + /* dummy check */ + if ( num < 0 ) { + return; + } + + /* get drawsurface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* get the mins/maxs for the dsurf */ + ClearBounds( mins, maxs ); + VectorCopy( bspDrawVerts[ ds->firstVert ].normal, normal ); + for ( i = 0; i < ds->numVerts; i++ ) + { + dv = &yDrawVerts[ ds->firstVert + i ]; + AddPointToBounds( dv->xyz, mins, maxs ); + if ( !VectorCompare( dv->normal, normal ) ) { + VectorClear( normal ); + } + } + + /* create the lights for the bounding box */ + CreateTraceLightsForBounds( mins, maxs, normal, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ], LIGHT_SURFACES, trace ); +} + +///////////////////////////////////////////////////////////// + +#define FLOODLIGHT_CONE_ANGLE 88 /* degrees */ +#define FLOODLIGHT_NUM_ANGLE_STEPS 16 +#define FLOODLIGHT_NUM_ELEVATION_STEPS 4 +#define FLOODLIGHT_NUM_VECTORS ( FLOODLIGHT_NUM_ANGLE_STEPS * FLOODLIGHT_NUM_ELEVATION_STEPS ) + +static vec3_t floodVectors[ FLOODLIGHT_NUM_VECTORS ]; +static int numFloodVectors = 0; + +void SetupFloodLight( void ){ + int i, j; + float angle, elevation, angleStep, elevationStep; + const char *value; + double v1,v2,v3,v4,v5,v6; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupFloodLight ---\n" ); + + /* calculate angular steps */ + angleStep = DEG2RAD( 360.0f / FLOODLIGHT_NUM_ANGLE_STEPS ); + elevationStep = DEG2RAD( FLOODLIGHT_CONE_ANGLE / FLOODLIGHT_NUM_ELEVATION_STEPS ); + + /* iterate angle */ + angle = 0.0f; + for ( i = 0, angle = 0.0f; i < FLOODLIGHT_NUM_ANGLE_STEPS; i++, angle += angleStep ) + { + /* iterate elevation */ + for ( j = 0, elevation = elevationStep * 0.5f; j < FLOODLIGHT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) + { + floodVectors[ numFloodVectors ][ 0 ] = sin( elevation ) * cos( angle ); + floodVectors[ numFloodVectors ][ 1 ] = sin( elevation ) * sin( angle ); + floodVectors[ numFloodVectors ][ 2 ] = cos( elevation ); + numFloodVectors++; + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d numFloodVectors\n", numFloodVectors ); + + /* floodlight */ + value = ValueForKey( &entities[ 0 ], "_floodlight" ); + + if ( value[ 0 ] != '\0' ) { + v1 = v2 = v3 = 0; + v4 = floodlightDistance; + v5 = floodlightIntensity; + v6 = floodlightDirectionScale; + + sscanf( value, "%lf %lf %lf %lf %lf %lf", &v1, &v2, &v3, &v4, &v5, &v6 ); + + floodlightRGB[0] = v1; + floodlightRGB[1] = v2; + floodlightRGB[2] = v3; + + if ( VectorLength( floodlightRGB ) == 0 ) { + VectorSet( floodlightRGB,0.94,0.94,1.0 ); + } + + if ( v4 < 1 ) { + v4 = 1024; + } + if ( v5 < 1 ) { + v5 = 128; + } + if ( v6 < 0 ) { + v6 = 1; + } + + floodlightDistance = v4; + floodlightIntensity = v5; + floodlightDirectionScale = v6; + + floodlighty = qtrue; + Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" ); + } + else + { + VectorSet( floodlightRGB,0.94,0.94,1.0 ); + } + if ( colorsRGB ) { + floodlightRGB[0] = Image_LinearFloatFromsRGBFloat( floodlightRGB[0] ); + floodlightRGB[1] = Image_LinearFloatFromsRGBFloat( floodlightRGB[1] ); + floodlightRGB[2] = Image_LinearFloatFromsRGBFloat( floodlightRGB[2] ); + } + ColorNormalize( floodlightRGB,floodlightRGB ); +} + +/* + FloodLightForSample() + calculates floodlight value for a given sample + once again, kudos to the dirtmapping coder + */ + +float FloodLightForSample( trace_t *trace, float floodLightDistance, qboolean floodLightLowQuality ){ + int i; + float d; + float contribution; + int sub = 0; + float gatherLight, outLight; + vec3_t normal, worldUp, myUp, myRt, direction, displacement; + float dd; + int vecs = 0; + + gatherLight = 0; + /* dummy check */ + //if( !dirty ) + // return 1.0f; + if ( trace == NULL || trace->cluster < 0 ) { + return 0.0f; + } + + + /* setup */ + dd = floodLightDistance; + VectorCopy( trace->normal, normal ); + + /* check if the normal is aligned to the world-up */ + if ( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f && ( normal[ 2 ] == 1.0f || normal[ 2 ] == -1.0f ) ) { + if ( normal[ 2 ] == 1.0f ) { + VectorSet( myRt, 1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + else if ( normal[ 2 ] == -1.0f ) { + VectorSet( myRt, -1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + } + else + { + VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); + CrossProduct( normal, worldUp, myRt ); + VectorNormalize( myRt, myRt ); + CrossProduct( myRt, normal, myUp ); + VectorNormalize( myUp, myUp ); + } + + /* vortex: optimise floodLightLowQuality a bit */ + if ( floodLightLowQuality == qtrue ) { + /* iterate through ordered vectors */ + for ( i = 0; i < numFloodVectors; i++ ) + if ( rand() % 10 != 0 ) { + continue; + } + } + else + { + /* iterate through ordered vectors */ + for ( i = 0; i < numFloodVectors; i++ ) + { + vecs++; + + /* transform vector into tangent space */ + direction[ 0 ] = myRt[ 0 ] * floodVectors[ i ][ 0 ] + myUp[ 0 ] * floodVectors[ i ][ 1 ] + normal[ 0 ] * floodVectors[ i ][ 2 ]; + direction[ 1 ] = myRt[ 1 ] * floodVectors[ i ][ 0 ] + myUp[ 1 ] * floodVectors[ i ][ 1 ] + normal[ 1 ] * floodVectors[ i ][ 2 ]; + direction[ 2 ] = myRt[ 2 ] * floodVectors[ i ][ 0 ] + myUp[ 2 ] * floodVectors[ i ][ 1 ] + normal[ 2 ] * floodVectors[ i ][ 2 ]; + + /* set endpoint */ + VectorMA( trace->origin, dd, direction, trace->end ); + + //VectorMA( trace->origin, 1, direction, trace->origin ); + + SetupTrace( trace ); + VectorSet(trace->color, 1.0f, 1.0f, 1.0f); + /* trace */ + TraceLine( trace ); + contribution = 1; + + if ( trace->compileFlags & C_SKY || trace->compileFlags & C_TRANSLUCENT ) { + contribution = 1.0f; + } + else if ( trace->opaque ) { + VectorSubtract( trace->hit, trace->origin, displacement ); + d = VectorLength( displacement ); + + // d=trace->distance; + //if (d>256) gatherDirt+=1; + contribution = d / dd; + if ( contribution > 1 ) { + contribution = 1.0f; + } + + //gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + + gatherLight += contribution; + } + } + + /* early out */ + if ( gatherLight <= 0.0f ) { + return 0.0f; + } + + sub = vecs; + + if ( sub < 1 ) { + sub = 1; + } + gatherLight /= ( sub ); + + outLight = gatherLight; + if ( outLight > 1.0f ) { + outLight = 1.0f; + } + + /* return to sender */ + return outLight; +} + +/* + FloodLightRawLightmap + lighttracer style ambient occlusion light hack. + Kudos to the dirtmapping author for most of this source. + VorteX: modified to floodlight up custom surfaces (q3map_floodLight) + VorteX: fixed problems with deluxemapping + */ + +// floodlight pass on a lightmap +void FloodLightRawLightmapPass( rawLightmap_t *lm, vec3_t lmFloodLightRGB, float lmFloodLightIntensity, float lmFloodLightDistance, qboolean lmFloodLightLowQuality, float floodlightDirectionScale ){ + int i, x, y, *cluster; + float *origin, *normal, *floodlight, floodLightAmount; + surfaceInfo_t *info; + trace_t trace; + // int sx, sy; + // float samples, average, *floodlight2; + + memset( &trace,0,sizeof( trace_t ) ); + + /* setup trace */ + trace.testOcclusion = qtrue; + trace.forceSunlight = qfalse; + trace.twoSided = qtrue; + trace.recvShadows = lm->recvShadows; + trace.numSurfaces = lm->numLightSurfaces; + trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + trace.testAll = qfalse; + trace.distance = 1024; + + /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ + //trace.twoSided = qfalse; + for ( i = 0; i < trace.numSurfaces; i++ ) + { + /* get surface */ + info = &surfaceInfos[ trace.surfaces[ i ] ]; + + /* check twosidedness */ + if ( info->si->twoSided ) { + trace.twoSided = qtrue; + break; + } + } + + /* gather floodlight */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + floodlight = SUPER_FLOODLIGHT( x, y ); + + /* set default dirt */ + *floodlight = 0.0f; + + /* only look at mapped luxels */ + if ( *cluster < 0 ) { + continue; + } + + /* copy to trace */ + trace.cluster = *cluster; + VectorCopy( origin, trace.origin ); + VectorCopy( normal, trace.normal ); + + /* get floodlight */ + floodLightAmount = FloodLightForSample( &trace, lmFloodLightDistance, lmFloodLightLowQuality ) * lmFloodLightIntensity; + + /* add floodlight */ + floodlight[0] += lmFloodLightRGB[0] * floodLightAmount; + floodlight[1] += lmFloodLightRGB[1] * floodLightAmount; + floodlight[2] += lmFloodLightRGB[2] * floodLightAmount; + floodlight[3] += floodlightDirectionScale; + } + } + + /* testing no filtering */ + return; + +#if 0 + + /* filter "dirt" */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + floodlight = SUPER_FLOODLIGHT( x, y ); + + /* filter dirt by adjacency to unmapped luxels */ + average = *floodlight; + samples = 1.0f; + for ( sy = ( y - 1 ); sy <= ( y + 1 ); sy++ ) + { + if ( sy < 0 || sy >= lm->sh ) { + continue; + } + + for ( sx = ( x - 1 ); sx <= ( x + 1 ); sx++ ) + { + if ( sx < 0 || sx >= lm->sw || ( sx == x && sy == y ) ) { + continue; + } + + /* get neighboring luxel */ + cluster = SUPER_CLUSTER( sx, sy ); + floodlight2 = SUPER_FLOODLIGHT( sx, sy ); + if ( *cluster < 0 || *floodlight2 <= 0.0f ) { + continue; + } + + /* add it */ + average += *floodlight2; + samples += 1.0f; + } + + /* bail */ + if ( samples <= 0.0f ) { + break; + } + } + + /* bail */ + if ( samples <= 0.0f ) { + continue; + } + + /* scale dirt */ + *floodlight = average / samples; + } + } +#endif +} + +void FloodLightRawLightmap( int rawLightmapNum ){ + rawLightmap_t *lm; + + /* bail if this number exceeds the number of raw lightmaps */ + if ( rawLightmapNum >= numRawLightmaps ) { + return; + } + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + /* global pass */ + if ( floodlighty && floodlightIntensity ) { + FloodLightRawLightmapPass( lm, floodlightRGB, floodlightIntensity, floodlightDistance, floodlight_lowquality, floodlightDirectionScale ); + } + + /* custom pass */ + if ( lm->floodlightIntensity ) { + FloodLightRawLightmapPass( lm, lm->floodlightRGB, lm->floodlightIntensity, lm->floodlightDistance, qfalse, lm->floodlightDirectionScale ); + numSurfacesFloodlighten += 1; + } +} + +void FloodlightRawLightmaps(){ + Sys_Printf( "--- FloodlightRawLightmap ---\n" ); + numSurfacesFloodlighten = 0; + RunThreadsOnIndividual( numRawLightmaps, qtrue, FloodLightRawLightmap ); + Sys_Printf( "%9d custom lightmaps floodlighted\n", numSurfacesFloodlighten ); +} + +/* + FloodLightIlluminate() + illuminate floodlight into lightmap luxels + */ + +void FloodlightIlluminateLightmap( rawLightmap_t *lm ){ + float *luxel, *floodlight, *deluxel, *normal; + int *cluster; + float brightness; + int x, y, lightmapNum; + + /* walk lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + continue; + } + + /* apply floodlight to each luxel */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get floodlight */ + floodlight = SUPER_FLOODLIGHT( x, y ); + if ( !floodlight[0] && !floodlight[1] && !floodlight[2] ) { + continue; + } + + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + + /* only process mapped luxels */ + if ( *cluster < 0 ) { + continue; + } + + /* get particulars */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* add to lightmap */ + luxel[0] += floodlight[0]; + luxel[1] += floodlight[1]; + luxel[2] += floodlight[2]; + + if ( luxel[3] == 0 ) { + luxel[3] = 1; + } + + /* add to deluxemap */ + if ( deluxemap && floodlight[3] > 0 ) { + vec3_t lightvector; + + normal = SUPER_NORMAL( x, y ); + brightness = RGBTOGRAY( floodlight ) * ( 1.0f / 255.0f ) * floodlight[3]; + + // use AT LEAST this amount of contribution from ambient for the deluxemap, fixes points that receive ZERO light + if ( brightness < 0.00390625f ) { + brightness = 0.00390625f; + } + + VectorScale( normal, brightness, lightvector ); + VectorAdd( deluxel, lightvector, deluxel ); + } + } + } + } +} diff --git a/tools/vmap/lightmaps.c b/tools/vmap/lightmaps.c new file mode 100644 index 0000000..34213d1 --- /dev/null +++ b/tools/vmap/lightmaps.c @@ -0,0 +1,115 @@ +/* + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "qbsp.h" + + +/* + + Lightmap allocation has to be done after all flood filling and + visible surface determination. + + */ + +int numSortShaders; +mapDrawSurface_t **surfsOnShader; +int allocatedSurfsOnShader; + + +int allocated[ LIGHTMAP_WIDTH ]; + +int numLightmaps = 1; +int c_exactLightmap = 0; +int c_planarPatch = 0; +int c_nonplanarLightmap = 0; + + +void PrepareNewLightmap( void ) { + memset( allocated, 0, sizeof( allocated ) ); + numLightmaps++; +} + +/* + =============== + AllocLMBlock + + returns a texture number and the position inside it + =============== + */ +qboolean AllocLMBlock( int w, int h, int *x, int *y ){ + int i, j; + int best, best2; + + best = LIGHTMAP_HEIGHT; + + for ( i = 0 ; i <= LIGHTMAP_WIDTH - w ; i++ ) { + best2 = 0; + + for ( j = 0 ; j < w ; j++ ) { + if ( allocated[i + j] >= best ) { + break; + } + if ( allocated[i + j] > best2 ) { + best2 = allocated[i + j]; + } + } + if ( j == w ) { // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if ( best + h > LIGHTMAP_HEIGHT ) { + return qfalse; + } + + for ( i = 0 ; i < w ; i++ ) { + allocated[*x + i] = best + h; + } + + return qtrue; +} + + +/* + =================== + AllocateLightmapForPatch + =================== + */ +//#define LIGHTMAP_PATCHSHIFT + + + +/* + =================== + AllocateLightmapForSurface + =================== + */ + +//#define LIGHTMAP_BLOCK 16 + + + +/* + =================== + AllocateLightmaps + =================== + */ diff --git a/tools/vmap/lightmaps_ydnar.c b/tools/vmap/lightmaps_ydnar.c new file mode 100644 index 0000000..bd44a24 --- /dev/null +++ b/tools/vmap/lightmaps_ydnar.c @@ -0,0 +1,3745 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHTMAPS_YDNAR_C + + + +/* dependencies */ +#include "vmap.h" +#include + + + +/* ------------------------------------------------------------------------------- + + this file contains code that doe lightmap allocation and projection that + runs in the -light phase. + + this is handled here rather than in the bsp phase for a few reasons-- + surfaces are no longer necessarily convex polygons, patches may or may not be + planar or have lightmaps projected directly onto control points. + + also, this allows lightmaps to be calculated before being allocated and stored + in the bsp. lightmaps that have little high-frequency information are candidates + for having their resolutions scaled down. + + ------------------------------------------------------------------------------- */ + +/* + WriteTGA24() + based on WriteTGA() from imagelib.c + */ + +void WriteTGA24( char *filename, byte *data, int width, int height, qboolean flip ){ + int i, c; + byte *buffer, *in; + FILE *file; + + + /* allocate a buffer and set it up */ + buffer = safe_malloc( width * height * 3 + 18 ); + memset( buffer, 0, 18 ); + buffer[ 2 ] = 2; + buffer[ 12 ] = width & 255; + buffer[ 13 ] = width >> 8; + buffer[ 14 ] = height & 255; + buffer[ 15 ] = height >> 8; + buffer[ 16 ] = 24; + + /* swap rgb to bgr */ + c = ( width * height * 3 ) + 18; + for ( i = 18; i < c; i += 3 ) + { + buffer[ i ] = data[ i - 18 + 2 ]; /* blue */ + buffer[ i + 1 ] = data[ i - 18 + 1 ]; /* green */ + buffer[ i + 2 ] = data[ i - 18 + 0 ]; /* red */ + } + + /* write it and free the buffer */ + file = fopen( filename, "wb" ); + if ( file == NULL ) { + Error( "Unable to open %s for writing", filename ); + } + + /* flip vertically? */ + if ( flip ) { + fwrite( buffer, 1, 18, file ); + for ( in = buffer + ( ( height - 1 ) * width * 3 ) + 18; in >= buffer; in -= ( width * 3 ) ) + fwrite( in, 1, ( width * 3 ), file ); + } + else{ + fwrite( buffer, 1, c, file ); + } + + /* close the file */ + fclose( file ); + free( buffer ); +} + +static unsigned int PackE5BRG9(float *rgb, float one) +{ //5 bits exponent, 3*9 bits of mantissa. no sign bit. + int e = 0; + int r,g,b; + float scale; + float m = rgb[0];if(m= 0.5) + { //positive exponent + while (m >= (1<<(e)) && e < 30-15) //don't do nans. + e++; + } + else + { //negative exponent... + while (m < 1/(1<<-e) && e > -15) //don't do denormals. + e--; + } + + scale = pow(2, e-9); + scale *= one; + + r = rgb[0]/scale + 0.5; + if (r < 0) r = 0; + if (r > 0x1ff) r = 0x1ff; + g = rgb[1]/scale + 0.5; + if (g < 0) g = 0; + if (g > 0x1ff) g = 0x1ff; + b = rgb[2]/scale + 0.5; + if (b < 0) b = 0; + if (b > 0x1ff) b = 0x1ff; + + return ((e+15)<<27) | (b<<18) | (g<<9) | r; +} +/* + WriteHDR() + Writes a Khronos TeXture, using some hdr format... + Fun choices: e5b9g9r9, half-float, or just float. Either way, the input is regular floats. +*/ + +//input data is in floats. +void WriteHDR( char *filename, float *data, int width, int height, qboolean flip, int fmt ){ + int x,y; + float *in; + unsigned int imagesize, rowbytes; + FILE *file; + + /* write it and free the buffer */ + file = fopen( filename, "wb" ); + if ( file == NULL ) { + Error( "Unable to open %s for writing", filename ); + } + + if (fmt == 0) + { //tga bgr format + byte tmp[3]; + byte header[18]; + byte *buffer; + memset( header, 0, 18 ); + header[ 2 ] = 2; + header[ 12 ] = width & 255; + header[ 13 ] = width >> 8; + header[ 14 ] = height & 255; + header[ 15 ] = height >> 8; + header[ 16 ] = 24; + fwrite( header, 1, sizeof(header), file ); + + rowbytes = width*3; + buffer = safe_malloc( rowbytes ); + for (y = 0; y < height; y++) + { + if (flip) + in = data + (height-y-1)*width*3; + else + in = data + y*width*3; + for (x = 0; x < width*3; x+=3) + { //spit out bgr8 packed data + ColorToBytes(in+x, tmp, 1); + buffer[x+0] = tmp[2]; + buffer[x+1] = tmp[1]; + buffer[x+2] = tmp[0]; + } + fwrite( buffer, 1, rowbytes, file ); + } + free( buffer ); + } + else + { + unsigned int *buffer; + vec3_t tmp; + struct + { + unsigned char magic[12]; + unsigned int endianness; + + unsigned int gltype; + unsigned int gltypesize; + unsigned int glformat; + unsigned int glinternalformat; + + unsigned int glbaseinternalformat; + unsigned int pixelwidth; + unsigned int pixelheight; + unsigned int pixeldepth; + + unsigned int numberofarrayelements; + unsigned int numberoffaces; + unsigned int numberofmipmaplevels; + unsigned int bytesofkeyvaluedata; + } header = { {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A}, + 0x04030201, + 0x8C3E/*GL_UNSIGNED_INT_5_9_9_9_REV_EXT*/, + 4, + 0x1907/*GL_RGB*/, + 0x8C3D/*GL_RGB9_E5*/, + 0x1907/*GL_RGB*/, + width, + height, + 0, //2d texture, so z size is 0. + 0, //not an array texture + 1, + 1, + 0}; + flip = !flip; //opengl technically defines textures as bottom-up, but screw that. + fwrite( &header, 1, sizeof(header), file ); + + rowbytes = width*4; //FIXME: align + imagesize = rowbytes*height; + fwrite( &imagesize, 1, sizeof(imagesize), file ); + + + buffer = safe_malloc( rowbytes ); + for (y = 0; y < height; y++) + { + if (flip) + in = data + (height-y-1)*width*3; + else + in = data + y*width*3; + for (x = 0; x < width; x++) + { + ColorToHDR(in+x*3, tmp); + buffer[x] = PackE5BRG9(in+x*3, 255/4.0); + } + fwrite( buffer, 1, rowbytes, file ); + } + free( buffer ); + } + + /* close the file */ + fclose( file ); +} + +/* + ExportLightmaps() + exports the lightmaps as a list of numbered tga images + */ + +void ExportLightmaps( void ){ + int i; + char dirname[ 1024 ], filename[ 1024+20 ]; + byte *lightmap; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- ExportLightmaps ---\n" ); + + /* do some path mangling */ + strcpy( dirname, source ); + StripExtension( dirname ); + + /* sanity check */ + if ( bspLightBytes == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: No BSP lightmap data\n" ); + return; + } + + /* make a directory for the lightmaps */ + Q_mkdir( dirname ); + + /* iterate through the lightmaps */ + for ( i = 0, lightmap = bspLightBytes; lightmap < ( bspLightBytes + numBSPLightBytes ); i++, lightmap += ( game->lightmapSize * game->lightmapSize * 3 ) ) + { + /* write a tga image out */ + sprintf( filename, "%s/lightmap_%04d.tga", dirname, i ); + Sys_Printf( "Writing %s\n", filename ); + WriteTGA24( filename, lightmap, game->lightmapSize, game->lightmapSize, qfalse ); + } +} + + + +/* + ExportLightmapsMain() + exports the lightmaps as a list of numbered tga images + */ + +int ExportLightmapsMain( int argc, char **argv ){ + /* arg checking */ + if ( argc < 1 ) { + Sys_Printf( "Usage: q3map -export [-v] \n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + + /* export the lightmaps */ + ExportLightmaps(); + + /* return to sender */ + return 0; +} + + + +/* + ImportLightmapsMain() + imports the lightmaps from a list of numbered tga images + */ + +int ImportLightmapsMain( int argc, char **argv ){ + int i, x, y, len, width, height; + char dirname[ 1024 ], filename[ 1024+20 ]; + byte *lightmap, *buffer, *pixels, *in, *out; + + + /* arg checking */ + if ( argc < 1 ) { + Sys_Printf( "Usage: q3map -import [-v] \n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- ImportLightmaps ---\n" ); + + /* do some path mangling */ + strcpy( dirname, source ); + StripExtension( dirname ); + + /* sanity check */ + if ( bspLightBytes == NULL ) { + Error( "No lightmap data" ); + } + + /* make a directory for the lightmaps */ + Q_mkdir( dirname ); + + /* iterate through the lightmaps */ + for ( i = 0, lightmap = bspLightBytes; lightmap < ( bspLightBytes + numBSPLightBytes ); i++, lightmap += ( game->lightmapSize * game->lightmapSize * 3 ) ) + { + /* read a tga image */ + sprintf( filename, "%s/lightmap_%04d.tga", dirname, i ); + Sys_Printf( "Loading %s\n", filename ); + buffer = NULL; + len = vfsLoadFile( filename, (void*) &buffer, -1 ); + if ( len < 0 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Unable to load image %s\n", filename ); + continue; + } + + /* parse file into an image */ + pixels = NULL; + LoadTGABuffer( buffer, buffer + len, &pixels, &width, &height ); + free( buffer ); + + /* sanity check it */ + if ( pixels == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Unable to load image %s\n", filename ); + continue; + } + if ( width != game->lightmapSize || height != game->lightmapSize ) { + Sys_FPrintf( SYS_WRN, "WARNING: Image %s is not the right size (%d, %d) != (%d, %d)\n", + filename, width, height, game->lightmapSize, game->lightmapSize ); + } + + /* copy the pixels */ + in = pixels; + for ( y = 1; y <= game->lightmapSize; y++ ) + { + out = lightmap + ( ( game->lightmapSize - y ) * game->lightmapSize * 3 ); + for ( x = 0; x < game->lightmapSize; x++, in += 4, out += 3 ) + VectorCopy( in, out ); + } + + /* free the image */ + free( pixels ); + } + + /* write the bsp */ + Sys_Printf( "writing %s\n", source ); + WriteBSPFile( source ); + + /* return to sender */ + return 0; +} + + + +/* ------------------------------------------------------------------------------- + + this section deals with projecting a lightmap onto a raw drawsurface + + ------------------------------------------------------------------------------- */ + +/* + CompareLightSurface() + compare function for qsort() + */ + +static int CompareLightSurface( const void *a, const void *b ){ + shaderInfo_t *asi, *bsi; + + + /* get shaders */ + asi = surfaceInfos[ *( (const int*) a ) ].si; + bsi = surfaceInfos[ *( (const int*) b ) ].si; + + /* dummy check */ + if ( asi == NULL ) { + return -1; + } + if ( bsi == NULL ) { + return 1; + } + + /* compare shader names */ + return strcmp( asi->shader, bsi->shader ); +} + + + +/* + FinishRawLightmap() + allocates a raw lightmap's necessary buffers + */ + +void FinishRawLightmap( rawLightmap_t *lm ){ + int i, j, c, size, *sc; + float is; + surfaceInfo_t *info; + + + /* sort light surfaces by shader name */ + qsort( &lightSurfaces[ lm->firstLightSurface ], lm->numLightSurfaces, sizeof( int ), CompareLightSurface ); + + /* count clusters */ + lm->numLightClusters = 0; + for ( i = 0; i < lm->numLightSurfaces; i++ ) + { + /* get surface info */ + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ]; + + /* add surface clusters */ + lm->numLightClusters += info->numSurfaceClusters; + } + + /* allocate buffer for clusters and copy */ + lm->lightClusters = safe_malloc( lm->numLightClusters * sizeof( *lm->lightClusters ) ); + c = 0; + for ( i = 0; i < lm->numLightSurfaces; i++ ) + { + /* get surface info */ + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ]; + + /* add surface clusters */ + for ( j = 0; j < info->numSurfaceClusters; j++ ) + lm->lightClusters[ c++ ] = surfaceClusters[ info->firstSurfaceCluster + j ]; + } + + /* set styles */ + lm->styles[ 0 ] = LS_NORMAL; + for ( i = 1; i < MAX_LIGHTMAPS; i++ ) + lm->styles[ i ] = LS_NONE; + + /* set supersampling size */ + lm->sw = lm->w * superSample; + lm->sh = lm->h * superSample; + + /* add to super luxel count */ + numRawSuperLuxels += ( lm->sw * lm->sh ); + + /* manipulate origin/vecs for supersampling */ + if ( superSample > 1 && lm->vecs != NULL ) { + /* calc inverse supersample */ + is = 1.0f / superSample; + + /* scale the vectors and shift the origin */ + #if 1 + /* new code that works for arbitrary supersampling values */ + VectorMA( lm->origin, -0.5, lm->vecs[ 0 ], lm->origin ); + VectorMA( lm->origin, -0.5, lm->vecs[ 1 ], lm->origin ); + VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] ); + VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] ); + VectorMA( lm->origin, is, lm->vecs[ 0 ], lm->origin ); + VectorMA( lm->origin, is, lm->vecs[ 1 ], lm->origin ); + #else + /* old code that only worked with a value of 2 */ + VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] ); + VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] ); + VectorMA( lm->origin, -is, lm->vecs[ 0 ], lm->origin ); + VectorMA( lm->origin, -is, lm->vecs[ 1 ], lm->origin ); + #endif + } + + /* allocate bsp lightmap storage */ + size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof( float ); + if ( lm->bspLuxels[ 0 ] == NULL ) { + lm->bspLuxels[ 0 ] = safe_malloc( size ); + } + memset( lm->bspLuxels[ 0 ], 0, size ); + + /* allocate radiosity lightmap storage */ + if ( bounce ) { + size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof( float ); + if ( lm->radLuxels[ 0 ] == NULL ) { + lm->radLuxels[ 0 ] = safe_malloc( size ); + } + memset( lm->radLuxels[ 0 ], 0, size ); + } + + /* allocate sampling lightmap storage */ + size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + if ( lm->superLuxels[ 0 ] == NULL ) { + lm->superLuxels[ 0 ] = safe_malloc( size ); + } + memset( lm->superLuxels[ 0 ], 0, size ); + + /* allocate origin map storage */ + size = lm->sw * lm->sh * SUPER_ORIGIN_SIZE * sizeof( float ); + if ( lm->superOrigins == NULL ) { + lm->superOrigins = safe_malloc( size ); + } + memset( lm->superOrigins, 0, size ); + + /* allocate normal map storage */ + size = lm->sw * lm->sh * SUPER_NORMAL_SIZE * sizeof( float ); + if ( lm->superNormals == NULL ) { + lm->superNormals = safe_malloc( size ); + } + memset( lm->superNormals, 0, size ); + + /* allocate floodlight map storage */ + size = lm->sw * lm->sh * SUPER_FLOODLIGHT_SIZE * sizeof( float ); + if ( lm->superFloodLight == NULL ) { + lm->superFloodLight = safe_malloc( size ); + } + memset( lm->superFloodLight, 0, size ); + + /* allocate cluster map storage */ + size = lm->sw * lm->sh * sizeof( int ); + if ( lm->superClusters == NULL ) { + lm->superClusters = safe_malloc( size ); + } + size = lm->sw * lm->sh; + sc = lm->superClusters; + for ( i = 0; i < size; i++ ) + ( *sc++ ) = CLUSTER_UNMAPPED; + + /* deluxemap allocation */ + if ( deluxemap ) { + /* allocate sampling deluxel storage */ + size = lm->sw * lm->sh * SUPER_DELUXEL_SIZE * sizeof( float ); + if ( lm->superDeluxels == NULL ) { + lm->superDeluxels = safe_malloc( size ); + } + memset( lm->superDeluxels, 0, size ); + + /* allocate bsp deluxel storage */ + size = lm->w * lm->h * BSP_DELUXEL_SIZE * sizeof( float ); + if ( lm->bspDeluxels == NULL ) { + lm->bspDeluxels = safe_malloc( size ); + } + memset( lm->bspDeluxels, 0, size ); + } + + /* add to count */ + numLuxels += ( lm->sw * lm->sh ); +} + + + +/* + AddPatchToRawLightmap() + projects a lightmap for a patch surface + since lightmap calculation for surfaces is now handled in a general way (light_ydnar.c), + it is no longer necessary for patch verts to fall exactly on a lightmap sample + based on AllocateLightmapForPatch() + */ + +qboolean AddPatchToRawLightmap( int num, rawLightmap_t *lm ){ + bspDrawSurface_t *ds; + surfaceInfo_t *info; + int x, y; + bspDrawVert_t *verts, *a, *b; + vec3_t delta; + mesh_t src, *subdivided, *mesh; + float sBasis, tBasis, s, t; + float length, widthTable[ MAX_EXPANDED_AXIS ], heightTable[ MAX_EXPANDED_AXIS ]; + + + /* patches finish a raw lightmap */ + lm->finished = qtrue; + + /* get surface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* make a temporary mesh from the drawsurf */ + if (ds->surfaceType == MST_PATCHFIXED) + { + src.width = ds->patchWidth&0xffff; + src.height = ds->patchHeight&0xffff; + src.subdiv_x = ds->patchWidth>>16; + src.subdiv_y = ds->patchHeight>>16; + } + else + { + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.subdiv_x = src.subdiv_y = -1; + } + src.verts = &yDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* find the longest distance on each row/column */ + verts = mesh->verts; + memset( widthTable, 0, sizeof( widthTable ) ); + memset( heightTable, 0, sizeof( heightTable ) ); + for ( y = 0; y < mesh->height; y++ ) + { + for ( x = 0; x < mesh->width; x++ ) + { + /* get width */ + if ( x + 1 < mesh->width ) { + a = &verts[ ( y * mesh->width ) + x ]; + b = &verts[ ( y * mesh->width ) + x + 1 ]; + VectorSubtract( a->xyz, b->xyz, delta ); + length = VectorLength( delta ); + if ( length > widthTable[ x ] ) { + widthTable[ x ] = length; + } + } + + /* get height */ + if ( y + 1 < mesh->height ) { + a = &verts[ ( y * mesh->width ) + x ]; + b = &verts[ ( ( y + 1 ) * mesh->width ) + x ]; + VectorSubtract( a->xyz, b->xyz, delta ); + length = VectorLength( delta ); + if ( length > heightTable[ y ] ) { + heightTable[ y ] = length; + } + } + } + } + + /* determine lightmap width */ + length = 0; + for ( x = 0; x < ( mesh->width - 1 ); x++ ) + length += widthTable[ x ]; + lm->w = lm->sampleSize != 0 ? ceil( length / lm->sampleSize ) + 1 : 0; + if ( lm->w < src.width ) { + lm->w = src.width; + } + if ( lm->w > lm->customWidth ) { + lm->w = lm->customWidth; + } + sBasis = (float) ( lm->w - 1 ) / (float) ( src.width - 1 ); + + /* determine lightmap height */ + length = 0; + for ( y = 0; y < ( mesh->height - 1 ); y++ ) + length += heightTable[ y ]; + lm->h = lm->sampleSize != 0 ? ceil( length / lm->sampleSize ) + 1 : 0; + if ( lm->h < src.height ) { + lm->h = src.height; + } + if ( lm->h > lm->customHeight ) { + lm->h = lm->customHeight; + } + tBasis = (float) ( lm->h - 1 ) / (float) ( src.height - 1 ); + + /* free the temporary mesh */ + FreeMesh( mesh ); + + /* set the lightmap texture coordinates in yDrawVerts */ + lm->wrap[ 0 ] = qtrue; + lm->wrap[ 1 ] = qtrue; + verts = &yDrawVerts[ ds->firstVert ]; + for ( y = 0; y < src.height; y++ ) + { + t = ( tBasis * y ) + 0.5f; + for ( x = 0; x < src.width; x++ ) + { + s = ( sBasis * x ) + 0.5f; + verts[ ( y * src.width ) + x ].lightmap[ 0 ][ 0 ] = s * superSample; + verts[ ( y * src.width ) + x ].lightmap[ 0 ][ 1 ] = t * superSample; + + if ( y == 0 && !VectorCompare( verts[ x ].xyz, verts[ ( ( src.height - 1 ) * src.width ) + x ].xyz ) ) { + lm->wrap[ 1 ] = qfalse; + } + } + + if ( !VectorCompare( verts[ ( y * src.width ) ].xyz, verts[ ( y * src.width ) + ( src.width - 1 ) ].xyz ) ) { + lm->wrap[ 0 ] = qfalse; + } + } + + /* debug code: */ + //% Sys_Printf( "wrap S: %d wrap T: %d\n", lm->wrap[ 0 ], lm->wrap[ 1 ] ); + //% if( lm->w > (ds->lightmapWidth & 0xFF) || lm->h > (ds->lightmapHeight & 0xFF) ) + //% Sys_Printf( "Patch lightmap: (%3d %3d) > (%3d, %3d)\n", lm->w, lm->h, ds->lightmapWidth & 0xFF, ds->lightmapHeight & 0xFF ); + //% ds->lightmapWidth = lm->w | (ds->lightmapWidth & 0xFFFF0000); + //% ds->lightmapHeight = lm->h | (ds->lightmapHeight & 0xFFFF0000); + + /* add to counts */ + numPatchesLightmapped++; + + /* return */ + return qtrue; +} + + + +/* + AddSurfaceToRawLightmap() + projects a lightmap for a surface + based on AllocateLightmapForSurface() + */ + +qboolean AddSurfaceToRawLightmap( int num, rawLightmap_t *lm ){ + bspDrawSurface_t *ds, *ds2; + surfaceInfo_t *info; + int num2, n, i, axisNum; + float s, t, d, len, sampleSize; + vec3_t mins, maxs, origin, faxis, size, delta, normalized, vecs[ 2 ]; + vec4_t plane; + bspDrawVert_t *verts; + + + /* get surface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* add the surface to the raw lightmap */ + lightSurfaces[ numLightSurfaces++ ] = num; + lm->numLightSurfaces++; + + /* does this raw lightmap already have any surfaces? */ + if ( lm->numLightSurfaces > 1 ) { + /* surface and raw lightmap must have the same lightmap projection axis */ + if ( VectorCompare( info->axis, lm->axis ) == qfalse ) { + return qfalse; + } + + /* match identical attributes */ + if ( info->sampleSize != lm->sampleSize || + info->entityNum != lm->entityNum || + info->recvShadows != lm->recvShadows || + info->si->lmCustomWidth != lm->customWidth || + info->si->lmCustomHeight != lm->customHeight || + info->si->lmBrightness != lm->brightness || + info->si->lmFilterRadius != lm->filterRadius || + info->si->splotchFix != lm->splotchFix ) { + return qfalse; + } + + /* surface bounds must intersect with raw lightmap bounds */ + for ( i = 0; i < 3; i++ ) + { + if ( info->mins[ i ] > lm->maxs[ i ] ) { + return qfalse; + } + if ( info->maxs[ i ] < lm->mins[ i ] ) { + return qfalse; + } + } + + /* plane check (fixme: allow merging of nonplanars) */ + if ( info->si->lmMergable == qfalse ) { + if ( info->plane == NULL || lm->plane == NULL ) { + return qfalse; + } + + /* compare planes */ + for ( i = 0; i < 4; i++ ) + if ( fabs( info->plane[ i ] - lm->plane[ i ] ) > EQUAL_EPSILON ) { + return qfalse; + } + } + + /* debug code hacking */ + //% if( lm->numLightSurfaces > 1 ) + //% return qfalse; + } + + /* set plane */ + if ( info->plane == NULL ) { + lm->plane = NULL; + } + + /* add surface to lightmap bounds */ + AddPointToBounds( info->mins, lm->mins, lm->maxs ); + AddPointToBounds( info->maxs, lm->mins, lm->maxs ); + + /* check to see if this is a non-planar patch */ + if ( (ds->surfaceType == MST_PATCH || ds->surfaceType == MST_PATCHFIXED) && + lm->axis[ 0 ] == 0.0f && lm->axis[ 1 ] == 0.0f && lm->axis[ 2 ] == 0.0f ) { + return AddPatchToRawLightmap( num, lm ); + } + + /* start with initially requested sample size */ + sampleSize = lm->sampleSize; + + /* round to the lightmap resolution */ + for ( i = 0; i < 3; i++ ) + { + mins[ i ] = sampleSize * floor( lm->mins[ i ] / sampleSize ); + maxs[ i ] = sampleSize * ceil( lm->maxs[ i ] / sampleSize ); + size[ i ] = ( maxs[ i ] - mins[ i ] ) / sampleSize + 1.0f; + + /* hack (god this sucks) */ + if ( size[ i ] > lm->customWidth || size[ i ] > lm->customHeight || ( lmLimitSize && size[i] > lmLimitSize ) ) { + i = -1; + sampleSize += 1.0f; + } + } + + if ( sampleSize != lm->sampleSize && lmLimitSize == 0 ) { + Sys_FPrintf( SYS_VRB,"WARNING: surface at (%6.0f %6.0f %6.0f) (%6.0f %6.0f %6.0f) too large for desired samplesize/lightmapsize/lightmapscale combination, increased samplesize from %d to %d\n", + info->mins[0], + info->mins[1], + info->mins[2], + info->maxs[0], + info->maxs[1], + info->maxs[2], + lm->sampleSize, + (int) sampleSize ); + } + + /* set actual sample size */ + lm->actualSampleSize = sampleSize; + + /* fixme: copy rounded mins/maxes to lightmap record? */ + if ( lm->plane == NULL ) { + VectorCopy( mins, lm->mins ); + VectorCopy( maxs, lm->maxs ); + VectorCopy( mins, origin ); + } + + /* set lightmap origin */ + VectorCopy( lm->mins, origin ); + + /* make absolute axis */ + faxis[ 0 ] = fabs( lm->axis[ 0 ] ); + faxis[ 1 ] = fabs( lm->axis[ 1 ] ); + faxis[ 2 ] = fabs( lm->axis[ 2 ] ); + + /* clear out lightmap vectors */ + memset( vecs, 0, sizeof( vecs ) ); + + /* classify the plane (x y or z major) (ydnar: biased to z axis projection) */ + if ( faxis[ 2 ] >= faxis[ 0 ] && faxis[ 2 ] >= faxis[ 1 ] ) { + axisNum = 2; + lm->w = size[ 0 ]; + lm->h = size[ 1 ]; + vecs[ 0 ][ 0 ] = 1.0f / sampleSize; + vecs[ 1 ][ 1 ] = 1.0f / sampleSize; + } + else if ( faxis[ 0 ] >= faxis[ 1 ] && faxis[ 0 ] >= faxis[ 2 ] ) { + axisNum = 0; + lm->w = size[ 1 ]; + lm->h = size[ 2 ]; + vecs[ 0 ][ 1 ] = 1.0f / sampleSize; + vecs[ 1 ][ 2 ] = 1.0f / sampleSize; + } + else + { + axisNum = 1; + lm->w = size[ 0 ]; + lm->h = size[ 2 ]; + vecs[ 0 ][ 0 ] = 1.0f / sampleSize; + vecs[ 1 ][ 2 ] = 1.0f / sampleSize; + } + + /* check for bogus axis */ + if ( faxis[ axisNum ] == 0.0f ) { + Sys_FPrintf( SYS_WRN, "WARNING: ProjectSurfaceLightmap: Chose a 0 valued axis\n" ); + lm->w = lm->h = 0; + return qfalse; + } + + /* store the axis number in the lightmap */ + lm->axisNum = axisNum; + + /* walk the list of surfaces on this raw lightmap */ + for ( n = 0; n < lm->numLightSurfaces; n++ ) + { + /* get surface */ + num2 = lightSurfaces[ lm->firstLightSurface + n ]; + ds2 = &bspDrawSurfaces[ num2 ]; + verts = &yDrawVerts[ ds2->firstVert ]; + + /* set the lightmap texture coordinates in yDrawVerts in [0, superSample * lm->customWidth] space */ + for ( i = 0; i < ds2->numVerts; i++ ) + { + VectorSubtract( verts[ i ].xyz, origin, delta ); + s = DotProduct( delta, vecs[ 0 ] ) + 0.5f; + t = DotProduct( delta, vecs[ 1 ] ) + 0.5f; + verts[ i ].lightmap[ 0 ][ 0 ] = s * superSample; + verts[ i ].lightmap[ 0 ][ 1 ] = t * superSample; + + if ( s > (float) lm->w || t > (float) lm->h ) { + Sys_FPrintf( SYS_VRB, "WARNING: Lightmap texture coords out of range: S %1.4f > %3d || T %1.4f > %3d\n", + s, lm->w, t, lm->h ); + } + } + } + + /* get first drawsurface */ + num2 = lightSurfaces[ lm->firstLightSurface ]; + ds2 = &bspDrawSurfaces[ num2 ]; + verts = &yDrawVerts[ ds2->firstVert ]; + + /* calculate lightmap origin */ + if ( VectorLength( ds2->lightmapVecs[ 2 ] ) ) { + VectorCopy( ds2->lightmapVecs[ 2 ], plane ); + } + else{ + VectorCopy( lm->axis, plane ); + } + plane[ 3 ] = DotProduct( verts[ 0 ].xyz, plane ); + + VectorCopy( origin, lm->origin ); + d = DotProduct( lm->origin, plane ) - plane[ 3 ]; + d /= plane[ axisNum ]; + lm->origin[ axisNum ] -= d; + + /* legacy support */ + VectorCopy( lm->origin, ds->lightmapOrigin ); + + /* for planar surfaces, create lightmap vectors for st->xyz conversion */ + if ( VectorLength( ds->lightmapVecs[ 2 ] ) || 1 ) { /* ydnar: can't remember what exactly i was thinking here... */ + /* allocate space for the vectors */ + lm->vecs = safe_malloc( 3 * sizeof( vec3_t ) ); + memset( lm->vecs, 0, 3 * sizeof( vec3_t ) ); + VectorCopy( ds->lightmapVecs[ 2 ], lm->vecs[ 2 ] ); + + /* project stepped lightmap blocks and subtract to get planevecs */ + for ( i = 0; i < 2; i++ ) + { + len = VectorNormalize( vecs[ i ], normalized ); + VectorScale( normalized, ( 1.0 / len ), lm->vecs[ i ] ); + d = DotProduct( lm->vecs[ i ], plane ); + d /= plane[ axisNum ]; + lm->vecs[ i ][ axisNum ] -= d; + } + } + else + { + /* lightmap vectors are useless on a non-planar surface */ + lm->vecs = NULL; + } + + /* add to counts */ + if ( ds->surfaceType == MST_PATCH||ds->surfaceType == MST_PATCHFIXED ) { + numPatchesLightmapped++; + if ( lm->plane != NULL ) { + numPlanarPatchesLightmapped++; + } + } + else + { + if ( lm->plane != NULL ) { + numPlanarsLightmapped++; + } + else{ + numNonPlanarsLightmapped++; + } + } + + /* return */ + return qtrue; +} + + + +/* + CompareSurfaceInfo() + compare function for qsort() + */ + +static int CompareSurfaceInfo( const void *a, const void *b ){ + surfaceInfo_t *aInfo, *bInfo; + int i; + + + /* get surface info */ + aInfo = &surfaceInfos[ *( (const int*) a ) ]; + bInfo = &surfaceInfos[ *( (const int*) b ) ]; + + /* model first */ + if ( aInfo->modelindex < bInfo->modelindex ) { + return 1; + } + else if ( aInfo->modelindex > bInfo->modelindex ) { + return -1; + } + + /* then lightmap status */ + if ( aInfo->hasLightmap < bInfo->hasLightmap ) { + return 1; + } + else if ( aInfo->hasLightmap > bInfo->hasLightmap ) { + return -1; + } + + /* 27: then shader! */ + if ( aInfo->si < bInfo->si ) { + return 1; + } + else if ( aInfo->si > bInfo->si ) { + return -1; + } + + /* then lightmap sample size */ + if ( aInfo->sampleSize < bInfo->sampleSize ) { + return 1; + } + else if ( aInfo->sampleSize > bInfo->sampleSize ) { + return -1; + } + + /* then lightmap axis */ + for ( i = 0; i < 3; i++ ) + { + if ( aInfo->axis[ i ] < bInfo->axis[ i ] ) { + return 1; + } + else if ( aInfo->axis[ i ] > bInfo->axis[ i ] ) { + return -1; + } + } + + /* then plane */ + if ( aInfo->plane == NULL && bInfo->plane != NULL ) { + return 1; + } + else if ( aInfo->plane != NULL && bInfo->plane == NULL ) { + return -1; + } + else if ( aInfo->plane != NULL && bInfo->plane != NULL ) { + for ( i = 0; i < 4; i++ ) + { + if ( aInfo->plane[ i ] < bInfo->plane[ i ] ) { + return 1; + } + else if ( aInfo->plane[ i ] > bInfo->plane[ i ] ) { + return -1; + } + } + } + + /* then position in world */ + for ( i = 0; i < 3; i++ ) + { + if ( aInfo->mins[ i ] < bInfo->mins[ i ] ) { + return 1; + } + else if ( aInfo->mins[ i ] > bInfo->mins[ i ] ) { + return -1; + } + } + + /* these are functionally identical (this should almost never happen) */ + return 0; +} + + + +/* + SetupSurfaceLightmaps() + allocates lightmaps for every surface in the bsp that needs one + this depends on yDrawVerts being allocated + */ + +void SetupSurfaceLightmaps( void ){ + int i, j, k, s,num, num2; + bspModel_t *model; + bspLeaf_t *leaf; + bspDrawSurface_t *ds; + surfaceInfo_t *info, *info2; + rawLightmap_t *lm; + qboolean added; + vec3_t mapSize, entityOrigin; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupSurfaceLightmaps ---\n" ); + + /* determine supersample amount */ + if ( superSample < 1 ) { + superSample = 1; + } + else if ( superSample > 8 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Insane supersampling amount (%d) detected.\n", superSample ); + superSample = 8; + } + + /* clear map bounds */ + ClearBounds( mapMins, mapMaxs ); + + /* allocate a list of surface clusters */ + numSurfaceClusters = 0; + maxSurfaceClusters = numBSPLeafSurfaces; + surfaceClusters = safe_malloc( maxSurfaceClusters * sizeof( *surfaceClusters ) ); + memset( surfaceClusters, 0, maxSurfaceClusters * sizeof( *surfaceClusters ) ); + + /* allocate a list for per-surface info */ + surfaceInfos = safe_malloc( numBSPDrawSurfaces * sizeof( *surfaceInfos ) ); + memset( surfaceInfos, 0, numBSPDrawSurfaces * sizeof( *surfaceInfos ) ); + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + surfaceInfos[ i ].childSurfaceNum = -1; + + /* allocate a list of surface indexes to be sorted */ + sortSurfaces = safe_malloc( numBSPDrawSurfaces * sizeof( int ) ); + memset( sortSurfaces, 0, numBSPDrawSurfaces * sizeof( int ) ); + + /* walk each model in the bsp */ + for ( i = 0; i < numBSPModels; i++ ) + { + /* get model */ + model = &bspModels[ i ]; + + /* walk the list of surfaces in this model and fill out the info structs */ + for ( j = 0; j < model->numBSPSurfaces; j++ ) + { + /* make surface index */ + num = model->firstBSPSurface + j; + + /* copy index to sort list */ + sortSurfaces[ num ] = num; + + /* get surface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* set entity origin */ + if ( ds->numVerts > 0 ) { + VectorSubtract( yDrawVerts[ ds->firstVert ].xyz, bspDrawVerts[ ds->firstVert ].xyz, entityOrigin ); + } + else{ + VectorClear( entityOrigin ); + } + + /* basic setup */ + info->modelindex = i; + info->lm = NULL; + info->plane = NULL; + info->firstSurfaceCluster = numSurfaceClusters; + + /* get extra data */ + info->si = GetSurfaceExtraShaderInfo( num ); + if ( info->si == NULL ) { + info->si = ShaderInfoForShader( bspShaders[ ds->shaderNum ].shader ); + } + info->parentSurfaceNum = GetSurfaceExtraParentSurfaceNum( num ); + info->entityNum = GetSurfaceExtraEntityNum( num ); + info->castShadows = GetSurfaceExtraCastShadows( num ); + info->recvShadows = GetSurfaceExtraRecvShadows( num ); + info->sampleSize = GetSurfaceExtraSampleSize( num ); + info->longestCurve = GetSurfaceExtraLongestCurve( num ); + info->patchIterations = IterationsForCurve( info->longestCurve, patchSubdivisions ); + GetSurfaceExtraLightmapAxis( num, info->axis ); + + /* mark parent */ + if ( info->parentSurfaceNum >= 0 ) { + surfaceInfos[ info->parentSurfaceNum ].childSurfaceNum = j; + } + + /* determine surface bounds */ + ClearBounds( info->mins, info->maxs ); + for ( k = 0; k < ds->numVerts; k++ ) + { + AddPointToBounds( yDrawVerts[ ds->firstVert + k ].xyz, mapMins, mapMaxs ); + AddPointToBounds( yDrawVerts[ ds->firstVert + k ].xyz, info->mins, info->maxs ); + } + + /* find all the bsp clusters the surface falls into */ + for ( k = 0; k < numBSPLeafs; k++ ) + { + /* get leaf */ + leaf = &bspLeafs[ k ]; + + /* test bbox */ + if ( leaf->mins[ 0 ] > info->maxs[ 0 ] || leaf->maxs[ 0 ] < info->mins[ 0 ] || + leaf->mins[ 1 ] > info->maxs[ 1 ] || leaf->maxs[ 1 ] < info->mins[ 1 ] || + leaf->mins[ 2 ] > info->maxs[ 2 ] || leaf->maxs[ 2 ] < info->mins[ 2 ] ) { + continue; + } + + /* test leaf surfaces */ + for ( s = 0; s < leaf->numBSPLeafSurfaces; s++ ) + { + if ( bspLeafSurfaces[ leaf->firstBSPLeafSurface + s ] == num ) { + if ( numSurfaceClusters >= maxSurfaceClusters ) { + Error( "maxSurfaceClusters exceeded" ); + } + surfaceClusters[ numSurfaceClusters ] = leaf->cluster; + numSurfaceClusters++; + info->numSurfaceClusters++; + } + } + } + + /* determine if surface is planar */ + if ( VectorLength( ds->lightmapVecs[ 2 ] ) > 0.0f ) { + /* make a plane */ + info->plane = safe_malloc( 4 * sizeof( float ) ); + VectorCopy( ds->lightmapVecs[ 2 ], info->plane ); + info->plane[ 3 ] = DotProduct( yDrawVerts[ ds->firstVert ].xyz, info->plane ); + } + + /* determine if surface requires a lightmap */ + if ( ds->surfaceType == MST_TRIANGLE_SOUP || + ds->surfaceType == MST_FOLIAGE || + ( info->si->compileFlags & C_VERTEXLIT ) ) { + numSurfsVertexLit++; + } + else + { + numSurfsLightmapped++; + info->hasLightmap = qtrue; + } + } + } + + /* find longest map distance */ + VectorSubtract( mapMaxs, mapMins, mapSize ); + maxMapDistance = VectorLength( mapSize ); + + /* sort the surfaces info list */ + qsort( sortSurfaces, numBSPDrawSurfaces, sizeof( int ), CompareSurfaceInfo ); + + /* allocate a list of surfaces that would go into raw lightmaps */ + numLightSurfaces = 0; + lightSurfaces = safe_malloc( numSurfsLightmapped * sizeof( int ) ); + memset( lightSurfaces, 0, numSurfsLightmapped * sizeof( int ) ); + + /* allocate a list of raw lightmaps */ + numRawSuperLuxels = 0; + numRawLightmaps = 0; + rawLightmaps = safe_malloc( numSurfsLightmapped * sizeof( *rawLightmaps ) ); + memset( rawLightmaps, 0, numSurfsLightmapped * sizeof( *rawLightmaps ) ); + + /* walk the list of sorted surfaces */ + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get info and attempt early out */ + num = sortSurfaces[ i ]; + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + if ( info->hasLightmap == qfalse || info->lm != NULL || info->parentSurfaceNum >= 0 ) { + continue; + } + + /* allocate a new raw lightmap */ + lm = &rawLightmaps[ numRawLightmaps ]; + numRawLightmaps++; + + /* set it up */ + lm->splotchFix = info->si->splotchFix; + lm->firstLightSurface = numLightSurfaces; + lm->numLightSurfaces = 0; + /* vortex: multiply lightmap sample size by -samplescale */ + if ( sampleScale > 0 ) { + lm->sampleSize = info->sampleSize * sampleScale; + } + else{ + lm->sampleSize = info->sampleSize; + } + lm->actualSampleSize = lm->sampleSize; + lm->entityNum = info->entityNum; + lm->recvShadows = info->recvShadows; + lm->brightness = info->si->lmBrightness; + lm->filterRadius = info->si->lmFilterRadius; + VectorCopy( info->si->floodlightRGB, lm->floodlightRGB ); + lm->floodlightDistance = info->si->floodlightDistance; + lm->floodlightIntensity = info->si->floodlightIntensity; + lm->floodlightDirectionScale = info->si->floodlightDirectionScale; + VectorCopy( info->axis, lm->axis ); + lm->plane = info->plane; + VectorCopy( info->mins, lm->mins ); + VectorCopy( info->maxs, lm->maxs ); + + lm->customWidth = info->si->lmCustomWidth; + lm->customHeight = info->si->lmCustomHeight; + + /* add the surface to the raw lightmap */ + AddSurfaceToRawLightmap( num, lm ); + info->lm = lm; + + /* do an exhaustive merge */ + added = qtrue; + while ( added ) + { + /* walk the list of surfaces again */ + added = qfalse; + for ( j = i + 1; j < numBSPDrawSurfaces && lm->finished == qfalse; j++ ) + { + /* get info and attempt early out */ + num2 = sortSurfaces[ j ]; + info2 = &surfaceInfos[ num2 ]; + if ( info2->hasLightmap == qfalse || info2->lm != NULL ) { + continue; + } + + /* add the surface to the raw lightmap */ + if ( AddSurfaceToRawLightmap( num2, lm ) ) { + info2->lm = lm; + added = qtrue; + } + else + { + /* back up one */ + lm->numLightSurfaces--; + numLightSurfaces--; + } + } + } + + /* finish the lightmap and allocate the various buffers */ + FinishRawLightmap( lm ); + } + + /* allocate vertex luxel storage */ + for ( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + vertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + memset( vertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + radVertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + memset( radVertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + } + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d surfaces\n", numBSPDrawSurfaces ); + Sys_FPrintf( SYS_VRB, "%9d raw lightmaps\n", numRawLightmaps ); + Sys_FPrintf( SYS_VRB, "%9d surfaces vertex lit\n", numSurfsVertexLit ); + Sys_FPrintf( SYS_VRB, "%9d surfaces lightmapped\n", numSurfsLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d planar surfaces lightmapped\n", numPlanarsLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d non-planar surfaces lightmapped\n", numNonPlanarsLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d patches lightmapped\n", numPatchesLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d planar patches lightmapped\n", numPlanarPatchesLightmapped ); +} + + + +/* + StitchSurfaceLightmaps() + stitches lightmap edges + 2002-11-20 update: use this func only for stitching nonplanar patch lightmap seams + */ + +#define MAX_STITCH_CANDIDATES 32 +#define MAX_STITCH_LUXELS 64 + +void StitchSurfaceLightmaps( void ){ + int i, j, x, y, x2, y2, *cluster, *cluster2, + numStitched, numCandidates, numLuxels, f, fOld, start; + rawLightmap_t *lm, *a, *b, *c[ MAX_STITCH_CANDIDATES ]; + float *luxel, *luxel2, *origin, *origin2, *normal, *normal2, + sampleSize, average[ 3 ], totalColor, ootc; + + + /* disabled for now */ + return; + + /* note it */ + Sys_Printf( "--- StitchSurfaceLightmaps ---\n" ); + + /* init pacifier */ + fOld = -1; + start = I_FloatTime(); + + /* walk the list of raw lightmaps */ + numStitched = 0; + for ( i = 0; i < numRawLightmaps; i++ ) + { + /* print pacifier */ + f = 10 * i / numRawLightmaps; + if ( f != fOld ) { + fOld = f; + Sys_Printf( "%i...", f ); + } + + /* get lightmap a */ + a = &rawLightmaps[ i ]; + + /* walk rest of lightmaps */ + numCandidates = 0; + for ( j = i + 1; j < numRawLightmaps && numCandidates < MAX_STITCH_CANDIDATES; j++ ) + { + /* get lightmap b */ + b = &rawLightmaps[ j ]; + + /* test bounding box */ + if ( a->mins[ 0 ] > b->maxs[ 0 ] || a->maxs[ 0 ] < b->mins[ 0 ] || + a->mins[ 1 ] > b->maxs[ 1 ] || a->maxs[ 1 ] < b->mins[ 1 ] || + a->mins[ 2 ] > b->maxs[ 2 ] || a->maxs[ 2 ] < b->mins[ 2 ] ) { + continue; + } + + /* add candidate */ + c[ numCandidates++ ] = b; + } + + /* walk luxels */ + for ( y = 0; y < a->sh; y++ ) + { + for ( x = 0; x < a->sw; x++ ) + { + /* ignore unmapped/unlit luxels */ + lm = a; + cluster = SUPER_CLUSTER( x, y ); + if ( *cluster == CLUSTER_UNMAPPED ) { + continue; + } + luxel = SUPER_LUXEL( 0, x, y ); + if ( luxel[ 3 ] <= 0.0f ) { + continue; + } + + /* get particulars */ + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + + /* walk candidate list */ + for ( j = 0; j < numCandidates; j++ ) + { + /* get candidate */ + b = c[ j ]; + lm = b; + + /* set samplesize to the smaller of the pair */ + sampleSize = 0.5f * ( a->actualSampleSize < b->actualSampleSize ? a->actualSampleSize : b->actualSampleSize ); + + /* test bounding box */ + if ( origin[ 0 ] < ( b->mins[ 0 ] - sampleSize ) || ( origin[ 0 ] > b->maxs[ 0 ] + sampleSize ) || + origin[ 1 ] < ( b->mins[ 1 ] - sampleSize ) || ( origin[ 1 ] > b->maxs[ 1 ] + sampleSize ) || + origin[ 2 ] < ( b->mins[ 2 ] - sampleSize ) || ( origin[ 2 ] > b->maxs[ 2 ] + sampleSize ) ) { + continue; + } + + /* walk candidate luxels */ + VectorClear( average ); + numLuxels = 0; + totalColor = 0.0f; + for ( y2 = 0; y2 < b->sh && numLuxels < MAX_STITCH_LUXELS; y2++ ) + { + for ( x2 = 0; x2 < b->sw && numLuxels < MAX_STITCH_LUXELS; x2++ ) + { + /* ignore same luxels */ + if ( a == b && abs( x - x2 ) <= 1 && abs( y - y2 ) <= 1 ) { + continue; + } + + /* ignore unmapped/unlit luxels */ + cluster2 = SUPER_CLUSTER( x2, y2 ); + if ( *cluster2 == CLUSTER_UNMAPPED ) { + continue; + } + luxel2 = SUPER_LUXEL( 0, x2, y2 ); + if ( luxel2[ 3 ] <= 0.0f ) { + continue; + } + + /* get particulars */ + origin2 = SUPER_ORIGIN( x2, y2 ); + normal2 = SUPER_NORMAL( x2, y2 ); + + /* test normal */ + if ( DotProduct( normal, normal2 ) < 0.5f ) { + continue; + } + + /* test bounds */ + if ( fabs( origin[ 0 ] - origin2[ 0 ] ) > sampleSize || + fabs( origin[ 1 ] - origin2[ 1 ] ) > sampleSize || + fabs( origin[ 2 ] - origin2[ 2 ] ) > sampleSize ) { + continue; + } + + /* add luxel */ + //% VectorSet( luxel2, 255, 0, 255 ); + VectorAdd( average, luxel2, average ); + totalColor += luxel2[ 3 ]; + } + } + + /* early out */ + if ( numLuxels == 0 ) { + continue; + } + + /* scale average */ + ootc = 1.0f / totalColor; + VectorScale( average, ootc, luxel ); + luxel[ 3 ] = 1.0f; + numStitched++; + } + } + } + } + + /* emit statistics */ + Sys_Printf( " (%i)\n", (int) ( I_FloatTime() - start ) ); + Sys_FPrintf( SYS_VRB, "%9d luxels stitched\n", numStitched ); +} + + + +/* + CompareBSPLuxels() + compares two surface lightmaps' bsp luxels, ignoring occluded luxels + */ + +#define SOLID_EPSILON 0.0625 +#define LUXEL_TOLERANCE 0.0025 +#define LUXEL_COLOR_FRAC 0.001302083 /* 1 / 3 / 256 */ + +static qboolean CompareBSPLuxels( rawLightmap_t *a, int aNum, rawLightmap_t *b, int bNum ){ + rawLightmap_t *lm; + int x, y; + double delta, total, rd, gd, bd; + float *aLuxel, *bLuxel; + + + /* styled lightmaps will never be collapsed to non-styled lightmaps when there is _minlight */ + if ( ( minLight[ 0 ] || minLight[ 1 ] || minLight[ 2 ] ) && + ( ( aNum == 0 && bNum != 0 ) || ( aNum != 0 && bNum == 0 ) ) ) { + return qfalse; + } + + /* basic tests */ + if ( a->customWidth != b->customWidth || a->customHeight != b->customHeight || + a->brightness != b->brightness || + a->solid[ aNum ] != b->solid[ bNum ] || + a->bspLuxels[ aNum ] == NULL || b->bspLuxels[ bNum ] == NULL ) { + return qfalse; + } + + /* compare solid color lightmaps */ + if ( a->solid[ aNum ] && b->solid[ bNum ] ) { + /* get deltas */ + rd = fabs( a->solidColor[ aNum ][ 0 ] - b->solidColor[ bNum ][ 0 ] ); + gd = fabs( a->solidColor[ aNum ][ 1 ] - b->solidColor[ bNum ][ 1 ] ); + bd = fabs( a->solidColor[ aNum ][ 2 ] - b->solidColor[ bNum ][ 2 ] ); + + /* compare color */ + if ( rd > SOLID_EPSILON || gd > SOLID_EPSILON || bd > SOLID_EPSILON ) { + return qfalse; + } + + /* okay */ + return qtrue; + } + + /* compare nonsolid lightmaps */ + if ( a->w != b->w || a->h != b->h ) { + return qfalse; + } + + /* compare luxels */ + delta = 0.0; + total = 0.0; + for ( y = 0; y < a->h; y++ ) + { + for ( x = 0; x < a->w; x++ ) + { + /* increment total */ + total += 1.0; + + /* get luxels */ + lm = a; aLuxel = BSP_LUXEL( aNum, x, y ); + lm = b; bLuxel = BSP_LUXEL( bNum, x, y ); + + /* ignore unused luxels */ + if ( aLuxel[ 0 ] < 0 || bLuxel[ 0 ] < 0 ) { + continue; + } + + /* get deltas */ + rd = fabs( aLuxel[ 0 ] - bLuxel[ 0 ] ); + gd = fabs( aLuxel[ 1 ] - bLuxel[ 1 ] ); + bd = fabs( aLuxel[ 2 ] - bLuxel[ 2 ] ); + + /* 2003-09-27: compare individual luxels */ + if ( rd > 3.0 || gd > 3.0 || bd > 3.0 ) { + return qfalse; + } + + /* compare (fixme: take into account perceptual differences) */ + delta += rd * LUXEL_COLOR_FRAC; + delta += gd * LUXEL_COLOR_FRAC; + delta += bd * LUXEL_COLOR_FRAC; + + /* is the change too high? */ + if ( total > 0.0 && ( ( delta / total ) > LUXEL_TOLERANCE ) ) { + return qfalse; + } + } + } + + /* made it this far, they must be identical (or close enough) */ + return qtrue; +} + + + +/* + MergeBSPLuxels() + merges two surface lightmaps' bsp luxels, overwriting occluded luxels + */ + +static qboolean MergeBSPLuxels( rawLightmap_t *a, int aNum, rawLightmap_t *b, int bNum ){ + rawLightmap_t *lm; + int x, y; + float luxel[ 3 ], *aLuxel, *bLuxel; + + + /* basic tests */ + if ( a->customWidth != b->customWidth || a->customHeight != b->customHeight || + a->brightness != b->brightness || + a->solid[ aNum ] != b->solid[ bNum ] || + a->bspLuxels[ aNum ] == NULL || b->bspLuxels[ bNum ] == NULL ) { + return qfalse; + } + + /* compare solid lightmaps */ + if ( a->solid[ aNum ] && b->solid[ bNum ] ) { + /* average */ + VectorAdd( a->solidColor[ aNum ], b->solidColor[ bNum ], luxel ); + VectorScale( luxel, 0.5f, luxel ); + + /* copy to both */ + VectorCopy( luxel, a->solidColor[ aNum ] ); + VectorCopy( luxel, b->solidColor[ bNum ] ); + + /* return to sender */ + return qtrue; + } + + /* compare nonsolid lightmaps */ + if ( a->w != b->w || a->h != b->h ) { + return qfalse; + } + + /* merge luxels */ + for ( y = 0; y < a->h; y++ ) + { + for ( x = 0; x < a->w; x++ ) + { + /* get luxels */ + lm = a; aLuxel = BSP_LUXEL( aNum, x, y ); + lm = b; bLuxel = BSP_LUXEL( bNum, x, y ); + + /* handle occlusion mismatch */ + if ( aLuxel[ 0 ] < 0.0f ) { + VectorCopy( bLuxel, aLuxel ); + } + else if ( bLuxel[ 0 ] < 0.0f ) { + VectorCopy( aLuxel, bLuxel ); + } + else + { + /* average */ + VectorAdd( aLuxel, bLuxel, luxel ); + VectorScale( luxel, 0.5f, luxel ); + + /* debugging code */ + //% luxel[ 2 ] += 64.0f; + + /* copy to both */ + VectorCopy( luxel, aLuxel ); + VectorCopy( luxel, bLuxel ); + } + } + } + + /* done */ + return qtrue; +} + + + +/* + ApproximateLuxel() + determines if a single luxel is can be approximated with the interpolated vertex rgba + */ + +static qboolean ApproximateLuxel( rawLightmap_t *lm, bspDrawVert_t *dv ){ + int i, x, y, d, lightmapNum; + float *luxel; + vec3_t color, vertexColor; + byte cb[ 4 ], vcb[ 4 ]; + + + /* find luxel xy coords */ + x = dv->lightmap[ 0 ][ 0 ] / superSample; + y = dv->lightmap[ 0 ][ 1 ] / superSample; + if ( x < 0 ) { + x = 0; + } + else if ( x >= lm->w ) { + x = lm->w - 1; + } + if ( y < 0 ) { + y = 0; + } + else if ( y >= lm->h ) { + y = lm->h - 1; + } + + /* walk list */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if ( lm->styles[ lightmapNum ] == LS_NONE ) { + continue; + } + + /* get luxel */ + luxel = BSP_LUXEL( lightmapNum, x, y ); + + /* ignore occluded luxels */ + if ( luxel[ 0 ] < 0.0f || luxel[ 1 ] < 0.0f || luxel[ 2 ] < 0.0f ) { + return qtrue; + } + + /* copy, set min color and compare */ + VectorCopy( luxel, color ); + VectorCopy( dv->color[ 0 ], vertexColor ); + + /* styles are not affected by minlight */ + if ( lightmapNum == 0 ) { + for ( i = 0; i < 3; i++ ) + { + /* set min color */ + if ( color[ i ] < minLight[ i ] ) { + color[ i ] = minLight[ i ]; + } + if ( vertexColor[ i ] < minLight[ i ] ) { /* note NOT minVertexLight */ + vertexColor[ i ] = minLight[ i ]; + } + } + } + + /* set to bytes */ + ColorToBytes( color, cb, 1.0f ); + ColorToBytes( vertexColor, vcb, 1.0f ); + + /* compare */ + for ( i = 0; i < 3; i++ ) + { + d = cb[ i ] - vcb[ i ]; + if ( d < 0 ) { + d *= -1; + } + if ( d > approximateTolerance ) { + return qfalse; + } + } + } + + /* close enough for the girls i date */ + return qtrue; +} + + + +/* + ApproximateTriangle() + determines if a single triangle can be approximated with vertex rgba + */ + +static qboolean ApproximateTriangle_r( rawLightmap_t *lm, bspDrawVert_t *dv[ 3 ] ){ + bspDrawVert_t mid, *dv2[ 3 ]; + int max; + + + /* approximate the vertexes */ + if ( ApproximateLuxel( lm, dv[ 0 ] ) == qfalse ) { + return qfalse; + } + if ( ApproximateLuxel( lm, dv[ 1 ] ) == qfalse ) { + return qfalse; + } + if ( ApproximateLuxel( lm, dv[ 2 ] ) == qfalse ) { + return qfalse; + } + + /* subdivide calc */ + { + int i; + float dx, dy, dist, maxDist; + + + /* find the longest edge and split it */ + max = -1; + maxDist = 0; + for ( i = 0; i < 3; i++ ) + { + dx = dv[ i ]->lightmap[ 0 ][ 0 ] - dv[ ( i + 1 ) % 3 ]->lightmap[ 0 ][ 0 ]; + dy = dv[ i ]->lightmap[ 0 ][ 1 ] - dv[ ( i + 1 ) % 3 ]->lightmap[ 0 ][ 1 ]; + dist = sqrt( ( dx * dx ) + ( dy * dy ) ); + if ( dist > maxDist ) { + maxDist = dist; + max = i; + } + } + + /* try to early out */ + if ( i < 0 || maxDist < subdivideThreshold ) { + return qtrue; + } + } + + /* split the longest edge and map it */ + LerpDrawVert( dv[ max ], dv[ ( max + 1 ) % 3 ], &mid ); + if ( ApproximateLuxel( lm, &mid ) == qfalse ) { + return qfalse; + } + + /* recurse to first triangle */ + VectorCopy( dv, dv2 ); + dv2[ max ] = ∣ + if ( ApproximateTriangle_r( lm, dv2 ) == qfalse ) { + return qfalse; + } + + /* recurse to second triangle */ + VectorCopy( dv, dv2 ); + dv2[ ( max + 1 ) % 3 ] = ∣ + return ApproximateTriangle_r( lm, dv2 ); +} + + + +/* + ApproximateLightmap() + determines if a raw lightmap can be approximated sufficiently with vertex colors + */ + +static qboolean ApproximateLightmap( rawLightmap_t *lm ){ + int n, num, i, x, y, pw[ 5 ], r; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + mesh_t src, *subdivided, *mesh; + bspDrawVert_t *verts, *dv[ 3 ]; + qboolean approximated; + + + /* approximating? */ + if ( approximateTolerance <= 0 ) { + return qfalse; + } + + /* test for jmonroe */ + #if 0 + /* don't approx lightmaps with styled twins */ + if ( lm->numStyledTwins > 0 ) { + return qfalse; + } + + /* don't approx lightmaps with styles */ + for ( i = 1; i < MAX_LIGHTMAPS; i++ ) + { + if ( lm->styles[ i ] != LS_NONE ) { + return qfalse; + } + } + #endif + + /* assume reduced until shadow detail is found */ + approximated = qtrue; + + /* walk the list of surfaces on this raw lightmap */ + for ( n = 0; n < lm->numLightSurfaces; n++ ) + { + /* get surface */ + num = lightSurfaces[ lm->firstLightSurface + n ]; + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* assume not-reduced initially */ + info->approximated = qfalse; + + /* bail if lightmap doesn't match up */ + if ( info->lm != lm ) { + continue; + } + + /* bail if not vertex lit */ + if ( info->si->noVertexLight ) { + continue; + } + + /* assume that surfaces whose bounding boxes is smaller than 2x samplesize will be forced to vertex */ + if ( ( info->maxs[ 0 ] - info->mins[ 0 ] ) <= ( 2.0f * info->sampleSize ) && + ( info->maxs[ 1 ] - info->mins[ 1 ] ) <= ( 2.0f * info->sampleSize ) && + ( info->maxs[ 2 ] - info->mins[ 2 ] ) <= ( 2.0f * info->sampleSize ) ) { + info->approximated = qtrue; + numSurfsVertexForced++; + continue; + } + + /* handle the triangles */ + switch ( ds->surfaceType ) + { + case MST_PLANAR: + /* get verts */ + verts = yDrawVerts + ds->firstVert; + + /* map the triangles */ + info->approximated = qtrue; + for ( i = 0; i < ds->numIndexes && info->approximated; i += 3 ) + { + dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ]; + dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ]; + dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ]; + info->approximated = ApproximateTriangle_r( lm, dv ); + } + break; + + case MST_PATCH: + case MST_PATCHFIXED: + /* make a mesh from the drawsurf */ + if (ds->surfaceType == MST_PATCHFIXED) + { + src.width = ds->patchWidth & 0xffff; + src.height = ds->patchHeight & 0xffff; + src.subdiv_x = ds->patchWidth>>16; + src.subdiv_y = ds->patchHeight>>16; + } + else + { + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.subdiv_x = src.subdiv_y = -1; + } + src.verts = &yDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* get verts */ + verts = mesh->verts; + + /* map the mesh quads */ + info->approximated = qtrue; + for ( y = 0; y < ( mesh->height - 1 ) && info->approximated; y++ ) + { + for ( x = 0; x < ( mesh->width - 1 ) && info->approximated; x++ ) + { + /* set indexes */ + pw[ 0 ] = x + ( y * mesh->width ); + pw[ 1 ] = x + ( ( y + 1 ) * mesh->width ); + pw[ 2 ] = x + 1 + ( ( y + 1 ) * mesh->width ); + pw[ 3 ] = x + 1 + ( y * mesh->width ); + pw[ 4 ] = x + ( y * mesh->width ); /* same as pw[ 0 ] */ + + /* set radix */ + r = ( x + y ) & 1; + + /* get drawverts and map first triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &verts[ pw[ r + 2 ] ]; + info->approximated = ApproximateTriangle_r( lm, dv ); + + /* get drawverts and map second triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 2 ] ]; + dv[ 2 ] = &verts[ pw[ r + 3 ] ]; + if ( info->approximated ) { + info->approximated = ApproximateTriangle_r( lm, dv ); + } + } + } + + /* free the mesh */ + FreeMesh( mesh ); + break; + + default: + break; + } + + /* reduced? */ + if ( info->approximated == qfalse ) { + approximated = qfalse; + } + else{ + numSurfsVertexApproximated++; + } + } + + /* return */ + return approximated; +} + + + +/* + TestOutLightmapStamp() + tests a stamp on a given lightmap for validity + */ + +static qboolean TestOutLightmapStamp( rawLightmap_t *lm, int lightmapNum, outLightmap_t *olm, int x, int y ){ + int sx, sy, ox, oy, offset; + float *luxel; + + + /* bounds check */ + if ( x < 0 || y < 0 || ( x + lm->w ) > olm->customWidth || ( y + lm->h ) > olm->customHeight ) { + return qfalse; + } + + /* solid lightmaps test a 1x1 stamp */ + if ( lm->solid[ lightmapNum ] ) { + offset = ( y * olm->customWidth ) + x; + if ( olm->lightBits[ offset >> 3 ] & ( 1 << ( offset & 7 ) ) ) { + return qfalse; + } + return qtrue; + } + + /* test the stamp */ + for ( sy = 0; sy < lm->h; sy++ ) + { + for ( sx = 0; sx < lm->w; sx++ ) + { + /* get luxel */ + luxel = BSP_LUXEL( lightmapNum, sx, sy ); + if ( luxel[ 0 ] < 0.0f ) { + continue; + } + + /* get bsp lightmap coords and test */ + ox = x + sx; + oy = y + sy; + offset = ( oy * olm->customWidth ) + ox; + if ( olm->lightBits[ offset >> 3 ] & ( 1 << ( offset & 7 ) ) ) { + return qfalse; + } + } + } + + /* stamp is empty */ + return qtrue; +} + + + +/* + SetupOutLightmap() + sets up an output lightmap + */ + +static void SetupOutLightmap( rawLightmap_t *lm, outLightmap_t *olm ){ + /* dummy check */ + if ( lm == NULL || olm == NULL ) { + return; + } + + /* is this a "normal" bsp-stored lightmap? */ + if ( ( lm->customWidth == game->lightmapSize && lm->customHeight == game->lightmapSize ) || externalLightmaps ) { + olm->lightmapNum = numBSPLightmaps; + numBSPLightmaps++; + + /* lightmaps are interleaved with light direction maps */ + if ( deluxemap ) { + numBSPLightmaps++; + } + } + else{ + olm->lightmapNum = -3; + } + + /* set external lightmap number */ + olm->extLightmapNum = -1; + + /* set it up */ + olm->numLightmaps = 0; + olm->customWidth = lm->customWidth; + olm->customHeight = lm->customHeight; + olm->freeLuxels = olm->customWidth * olm->customHeight; + olm->numShaders = 0; + + /* allocate buffers */ + olm->lightBits = safe_malloc( ( olm->customWidth * olm->customHeight / 8 ) + 8 ); + memset( olm->lightBits, 0, ( olm->customWidth * olm->customHeight / 8 ) + 8 ); +#ifdef LIGHTMAP_HDR + olm->bspLightHDR = safe_malloc( olm->customWidth * olm->customHeight * 3 * sizeof(float) ); + memset( olm->bspLightHDR, 0, olm->customWidth * olm->customHeight * 3 * sizeof(float) ); +#else + olm->bspLightBytes = safe_malloc( olm->customWidth * olm->customHeight * 3 ); + memset( olm->bspLightBytes, 0, olm->customWidth * olm->customHeight * 3 ); +#endif + if ( deluxemap ) { + olm->bspDirBytes = safe_malloc( olm->customWidth * olm->customHeight * 3 ); + memset( olm->bspDirBytes, 0, olm->customWidth * olm->customHeight * 3 ); + } +} + + + +/* + FindOutLightmaps() + for a given surface lightmap, find output lightmap pages and positions for it + */ + +#define LIGHTMAP_RESERVE_COUNT 1 +static void FindOutLightmaps( rawLightmap_t *lm, qboolean fastAllocate ){ + int i, j, k, lightmapNum, xMax, yMax, x = -1, y = -1, sx, sy, ox, oy, offset; + outLightmap_t *olm; + surfaceInfo_t *info; + float *luxel, *deluxel; + vec3_t color, direction; + float *fpixel; + byte *pixel; + qboolean ok; + int xIncrement, yIncrement; + + + /* set default lightmap number (-3 = LIGHTMAP_BY_VERTEX) */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + lm->outLightmapNums[ lightmapNum ] = -3; + + /* can this lightmap be approximated with vertex color? */ + if ( ApproximateLightmap( lm ) ) { + return; + } + + /* walk list */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if ( lm->styles[ lightmapNum ] == LS_NONE ) { + continue; + } + + /* don't store twinned lightmaps */ + if ( lm->twins[ lightmapNum ] != NULL ) { + continue; + } + + /* if this is a styled lightmap, try some normalized locations first */ + ok = qfalse; + if ( lightmapNum > 0 && outLightmaps != NULL ) { + /* loop twice */ + for ( j = 0; j < 2; j++ ) + { + /* try identical position */ + for ( i = 0; i < numOutLightmaps; i++ ) + { + /* get the output lightmap */ + olm = &outLightmaps[ i ]; + + /* simple early out test */ + if ( olm->freeLuxels < lm->used ) { + continue; + } + + /* don't store non-custom raw lightmaps on custom bsp lightmaps */ + if ( olm->customWidth != lm->customWidth || + olm->customHeight != lm->customHeight ) { + continue; + } + + /* try identical */ + if ( j == 0 ) { + x = lm->lightmapX[ 0 ]; + y = lm->lightmapY[ 0 ]; + ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y ); + } + + /* try shifting */ + else + { + for ( sy = -1; sy <= 1; sy++ ) + { + for ( sx = -1; sx <= 1; sx++ ) + { + x = lm->lightmapX[ 0 ] + sx * ( olm->customWidth >> 1 ); //% lm->w; + y = lm->lightmapY[ 0 ] + sy * ( olm->customHeight >> 1 ); //% lm->h; + ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y ); + + if ( ok ) { + break; + } + } + + if ( ok ) { + break; + } + } + } + + if ( ok ) { + break; + } + } + + if ( ok ) { + break; + } + } + } + + /* try normal placement algorithm */ + if ( ok == qfalse ) { + /* reset origin */ + x = 0; + y = 0; + + /* walk the list of lightmap pages */ + if ( lightmapSearchBlockSize <= 0 || numOutLightmaps < LIGHTMAP_RESERVE_COUNT ) { + i = 0; + } + else{ + i = ( ( numOutLightmaps - LIGHTMAP_RESERVE_COUNT ) / lightmapSearchBlockSize ) * lightmapSearchBlockSize; + } + for ( ; i < numOutLightmaps; i++ ) + { + /* get the output lightmap */ + olm = &outLightmaps[ i ]; + + /* simple early out test */ + if ( olm->freeLuxels < lm->used ) { + continue; + } + + /* if fast allocation, skip lightmap files that are more than 90% complete */ + if ( fastAllocate == qtrue ) { + if (olm->freeLuxels < (olm->customWidth * olm->customHeight) / 10) { + continue; + } + } + + /* don't store non-custom raw lightmaps on custom bsp lightmaps */ + if ( olm->customWidth != lm->customWidth || + olm->customHeight != lm->customHeight ) { + continue; + } + + /* set maxs */ + if ( lm->solid[ lightmapNum ] ) { + xMax = olm->customWidth; + yMax = olm->customHeight; + } + else + { + xMax = ( olm->customWidth - lm->w ) + 1; + yMax = ( olm->customHeight - lm->h ) + 1; + } + + /* if fast allocation, do not test allocation on every pixels, especially for large lightmaps */ + if ( fastAllocate == qtrue ) { + xIncrement = MAX(1, lm->w / 15); + yIncrement = MAX(1, lm->h / 15); + } + else { + xIncrement = 1; + yIncrement = 1; + } + + /* walk the origin around the lightmap */ + for ( y = 0; y < yMax; y += yIncrement ) + { + for ( x = 0; x < xMax; x += xIncrement ) + { + /* find a fine tract of lauhnd */ + ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y ); + + if ( ok ) { + break; + } + } + + if ( ok ) { + break; + } + } + + if ( ok ) { + break; + } + + /* reset x and y */ + x = 0; + y = 0; + } + } + + /* no match? */ + if ( ok == qfalse ) { + /* allocate LIGHTMAP_RESERVE_COUNT new output lightmaps */ + numOutLightmaps += LIGHTMAP_RESERVE_COUNT; + olm = safe_malloc( numOutLightmaps * sizeof( outLightmap_t ) ); + if ( outLightmaps != NULL && numOutLightmaps > LIGHTMAP_RESERVE_COUNT ) { + memcpy( olm, outLightmaps, ( numOutLightmaps - LIGHTMAP_RESERVE_COUNT ) * sizeof( outLightmap_t ) ); + free( outLightmaps ); + } + outLightmaps = olm; + + /* initialize both out lightmaps */ + for ( k = numOutLightmaps - LIGHTMAP_RESERVE_COUNT; k < numOutLightmaps; ++k ) + SetupOutLightmap( lm, &outLightmaps[ k ] ); + + /* set out lightmap */ + i = numOutLightmaps - LIGHTMAP_RESERVE_COUNT; + olm = &outLightmaps[ i ]; + + /* set stamp xy origin to the first surface lightmap */ + if ( lightmapNum > 0 ) { + x = lm->lightmapX[ 0 ]; + y = lm->lightmapY[ 0 ]; + } + } + + /* if this is a style-using lightmap, it must be exported */ + if ( lightmapNum > 0 && game->load != LoadRBSPFile ) { + olm->extLightmapNum = 0; + } + + /* add the surface lightmap to the bsp lightmap */ + lm->outLightmapNums[ lightmapNum ] = i; + lm->lightmapX[ lightmapNum ] = x; + lm->lightmapY[ lightmapNum ] = y; + olm->numLightmaps++; + + /* add shaders */ + for ( i = 0; i < lm->numLightSurfaces; i++ ) + { + /* get surface info */ + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ]; + + /* test for shader */ + for ( j = 0; j < olm->numShaders; j++ ) + { + if ( olm->shaders[ j ] == info->si ) { + break; + } + } + + /* if it doesn't exist, add it */ + if ( j >= olm->numShaders && olm->numShaders < MAX_LIGHTMAP_SHADERS ) { + olm->shaders[ olm->numShaders ] = info->si; + olm->numShaders++; + numLightmapShaders++; + } + } + + /* set maxs */ + if ( lm->solid[ lightmapNum ] ) { + xMax = 1; + yMax = 1; + } + else + { + xMax = lm->w; + yMax = lm->h; + } + + /* mark the bits used */ + for ( y = 0; y < yMax; y++ ) + { + for ( x = 0; x < xMax; x++ ) + { + /* get luxel */ + luxel = BSP_LUXEL( lightmapNum, x, y ); + deluxel = BSP_DELUXEL( x, y ); + if ( luxel[ 0 ] < 0.0f && !lm->solid[ lightmapNum ] ) { + continue; + } + + /* set minimum light */ + if ( lm->solid[ lightmapNum ] ) { + if ( debug ) { + VectorSet( color, 255.0f, 0.0f, 0.0f ); + } + else{ + VectorCopy( lm->solidColor[ lightmapNum ], color ); + } + } + else{ + VectorCopy( luxel, color ); + } + + /* styles are not affected by minlight */ + if ( lightmapNum == 0 ) { + for ( i = 0; i < 3; i++ ) + { + if ( color[ i ] < minLight[ i ] ) { + color[ i ] = minLight[ i ]; + } + } + } + + /* get bsp lightmap coords */ + ox = x + lm->lightmapX[ lightmapNum ]; + oy = y + lm->lightmapY[ lightmapNum ]; + offset = ( oy * olm->customWidth ) + ox; + + /* flag pixel as used */ + olm->lightBits[ offset >> 3 ] |= ( 1 << ( offset & 7 ) ); + olm->freeLuxels--; + + /* store color */ +#ifdef LIGHTMAP_HDR + fpixel = olm->bspLightHDR + ( ( ( oy * olm->customWidth ) + ox ) * 3 ); + VectorScale( color, ((lm->brightness<=0)?1.0f:lm->brightness), fpixel ); +#else + pixel = olm->bspLightBytes + ( ( ( oy * olm->customWidth ) + ox ) * 3 ); + ColorToBytes( color, pixel, lm->brightness ); +#endif + + /* store direction */ + if ( deluxemap ) { + /* normalize average light direction */ + pixel = olm->bspDirBytes + ( ( ( oy * olm->customWidth ) + ox ) * 3 ); + VectorScale( deluxel, 1000.0f, direction ); + VectorNormalize( direction, direction ); + VectorScale( direction, 127.5f, direction ); + for ( i = 0; i < 3; i++ ) + pixel[ i ] = (byte)( 127.5f + direction[ i ] ); + } + } + } + } +} + + + +/* + CompareRawLightmap() + compare function for qsort() + */ + +static int CompareRawLightmap( const void *a, const void *b ){ + rawLightmap_t *alm, *blm; + surfaceInfo_t *aInfo, *bInfo; + int i, min, diff; + + + /* get lightmaps */ + alm = &rawLightmaps[ *( (const int*) a ) ]; + blm = &rawLightmaps[ *( (const int*) b ) ]; + + /* get min number of surfaces */ + min = ( alm->numLightSurfaces < blm->numLightSurfaces ? alm->numLightSurfaces : blm->numLightSurfaces ); + + /* iterate */ + for ( i = 0; i < min; i++ ) + { + /* get surface info */ + aInfo = &surfaceInfos[ lightSurfaces[ alm->firstLightSurface + i ] ]; + bInfo = &surfaceInfos[ lightSurfaces[ blm->firstLightSurface + i ] ]; + + /* compare shader names */ + diff = strcmp( aInfo->si->shader, bInfo->si->shader ); + if ( diff != 0 ) { + return diff; + } + } + + /* test style count */ + diff = 0; + for ( i = 0; i < MAX_LIGHTMAPS; i++ ) + diff += blm->styles[ i ] - alm->styles[ i ]; + if ( diff ) { + return diff; + } + + /* compare size */ + diff = ( blm->w * blm->h ) - ( alm->w * alm->h ); + if ( diff != 0 ) { + return diff; + } + + /* must be equivalent */ + return 0; +} + + + +void FillOutLightmap( outLightmap_t *olm ){ + int x, y; + int ofs; + vec3_t dir_sum, light_sum; + int cnt, filled; + byte *lightBitsNew = NULL; +#ifdef LIGHTMAP_HDR + float *lightHDRNew = NULL; +#else + byte *lightBytesNew = NULL; +#endif + byte *dirBytesNew = NULL; + + lightBitsNew = safe_malloc( ( olm->customWidth * olm->customHeight + 8 ) / 8 ); +#ifdef LIGHTMAP_HDR + lightHDRNew = safe_malloc( olm->customWidth * olm->customHeight * 3 * sizeof(*lightHDRNew) ); +#else + lightBytesNew = safe_malloc( olm->customWidth * olm->customHeight * 3 ); +#endif + if ( deluxemap ) { + dirBytesNew = safe_malloc( olm->customWidth * olm->customHeight * 3 ); + } + + /* + memset(olm->lightBits, 0, (olm->customWidth * olm->customHeight + 8) / 8); + olm->lightBits[0] |= 1; + olm->lightBits[(10 * olm->customWidth + 30) >> 3] |= 1 << ((10 * olm->customWidth + 30) & 7); + memset(olm->bspLightBytes, 0, olm->customWidth * olm->customHeight * 3); + olm->bspLightBytes[0] = 255; + olm->bspLightBytes[(10 * olm->customWidth + 30) * 3 + 2] = 255; + */ + + memcpy( lightBitsNew, olm->lightBits, ( olm->customWidth * olm->customHeight + 8 ) / 8 ); +#ifdef LIGHTMAP_HDR + memcpy( lightHDRNew, olm->bspLightHDR, olm->customWidth * olm->customHeight * 3 * sizeof(*lightHDRNew) ); +#else + memcpy( lightBytesNew, olm->bspLightBytes, olm->customWidth * olm->customHeight * 3 ); +#endif + if ( deluxemap ) { + memcpy( dirBytesNew, olm->bspDirBytes, olm->customWidth * olm->customHeight * 3 ); + } + + for (;; ) + { + filled = 0; + for ( y = 0; y < olm->customHeight; ++y ) + { + for ( x = 0; x < olm->customWidth; ++x ) + { + ofs = y * olm->customWidth + x; + if ( olm->lightBits[ofs >> 3] & ( 1 << ( ofs & 7 ) ) ) { /* already filled */ + continue; + } + cnt = 0; + VectorClear( dir_sum ); + VectorClear( light_sum ); + + /* try all four neighbors */ + ofs = ( ( y + olm->customHeight - 1 ) % olm->customHeight ) * olm->customWidth + x; + if ( olm->lightBits[ofs >> 3] & ( 1 << ( ofs & 7 ) ) ) { /* already filled */ + ++cnt; +#ifdef LIGHTMAP_HDR + VectorAdd( light_sum, olm->bspLightHDR + ofs * 3, light_sum ); +#else + VectorAdd( light_sum, olm->bspLightBytes + ofs * 3, light_sum ); +#endif + if ( deluxemap ) { + VectorAdd( dir_sum, olm->bspDirBytes + ofs * 3, dir_sum ); + } + } + + ofs = ( ( y + 1 ) % olm->customHeight ) * olm->customWidth + x; + if ( olm->lightBits[ofs >> 3] & ( 1 << ( ofs & 7 ) ) ) { /* already filled */ + ++cnt; +#ifdef LIGHTMAP_HDR + VectorAdd( light_sum, olm->bspLightHDR + ofs * 3, light_sum ); +#else + VectorAdd( light_sum, olm->bspLightBytes + ofs * 3, light_sum ); +#endif + if ( deluxemap ) { + VectorAdd( dir_sum, olm->bspDirBytes + ofs * 3, dir_sum ); + } + } + + ofs = y * olm->customWidth + ( x + olm->customWidth - 1 ) % olm->customWidth; + if ( olm->lightBits[ofs >> 3] & ( 1 << ( ofs & 7 ) ) ) { /* already filled */ + ++cnt; +#ifdef LIGHTMAP_HDR + VectorAdd( light_sum, olm->bspLightHDR + ofs * 3, light_sum ); +#else + VectorAdd( light_sum, olm->bspLightBytes + ofs * 3, light_sum ); +#endif + if ( deluxemap ) { + VectorAdd( dir_sum, olm->bspDirBytes + ofs * 3, dir_sum ); + } + } + + ofs = y * olm->customWidth + ( x + 1 ) % olm->customWidth; + if ( olm->lightBits[ofs >> 3] & ( 1 << ( ofs & 7 ) ) ) { /* already filled */ + ++cnt; +#ifdef LIGHTMAP_HDR + VectorAdd( light_sum, olm->bspLightHDR + ofs * 3, light_sum ); +#else + VectorAdd( light_sum, olm->bspLightBytes + ofs * 3, light_sum ); +#endif + if ( deluxemap ) { + VectorAdd( dir_sum, olm->bspDirBytes + ofs * 3, dir_sum ); + } + } + + if ( cnt ) { + ++filled; + ofs = y * olm->customWidth + x; + lightBitsNew[ofs >> 3] |= ( 1 << ( ofs & 7 ) ); +#ifdef LIGHTMAP_HDR + VectorScale( light_sum, 1.0 / cnt, lightHDRNew + ofs * 3 ); +#else + VectorScale( light_sum, 1.0 / cnt, lightBytesNew + ofs * 3 ); +#endif + if ( deluxemap ) { + VectorScale( dir_sum, 1.0 / cnt, dirBytesNew + ofs * 3 ); + } + } + } + } + + if ( !filled ) { + break; + } + + memcpy( olm->lightBits, lightBitsNew, ( olm->customWidth * olm->customHeight + 8 ) / 8 ); +#ifdef LIGHTMAP_HDR + memcpy( olm->bspLightHDR, lightHDRNew, olm->customWidth * olm->customHeight * 3 * sizeof(*lightHDRNew) ); +#else + memcpy( olm->bspLightBytes, lightBytesNew, olm->customWidth * olm->customHeight * 3 ); +#endif + if ( deluxemap ) { + memcpy( olm->bspDirBytes, dirBytesNew, olm->customWidth * olm->customHeight * 3 ); + } + } + + free( lightBitsNew ); +#ifdef LIGHTMAP_HDR + free( lightHDRNew ); +#else + free( lightBytesNew ); +#endif + if ( deluxemap ) { + free( dirBytesNew ); + } +} + + + +/* + StoreSurfaceLightmaps() + stores the surface lightmaps into the bsp as byte rgb triplets + */ + +void StoreSurfaceLightmaps( qboolean fastAllocate ){ + int i, j, k, x, y, lx, ly, sx, sy, *cluster, mappedSamples; + int style, size, lightmapNum, lightmapNum2; + float *normal, *luxel, *bspLuxel, *bspLuxel2, *radLuxel, samples, occludedSamples; + vec3_t sample, occludedSample, dirSample, colorMins, colorMaxs; + float *deluxel, *bspDeluxel, *bspDeluxel2; + byte *lb; + int numUsed, numTwins, numTwinLuxels, numStored; + float lmx, lmy, efficiency; + vec3_t color; + bspDrawSurface_t *ds, *parent, dsTemp; + surfaceInfo_t *info; + rawLightmap_t *lm, *lm2; + outLightmap_t *olm; + bspDrawVert_t *dv, *ydv, *dvParent; + char dirname[ 1024 ], filename[ 1024+20 ]; + shaderInfo_t *csi; + char lightmapName[ 128 ]; + const char *rgbGenValues[ 256 ]; + const char *alphaGenValues[ 256 ]; + + + /* note it */ + Sys_Printf( "--- StoreSurfaceLightmaps ---\n" ); + + /* setup */ + if ( lmCustomDir ) { + strcpy( dirname, lmCustomDir ); + } + else + { + strcpy( dirname, source ); + StripExtension( dirname ); + } + memset( rgbGenValues, 0, sizeof( rgbGenValues ) ); + memset( alphaGenValues, 0, sizeof( alphaGenValues ) ); + + /* ----------------------------------------------------------------- + average the sampled luxels into the bsp luxels + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "Subsampling..." ); + + /* walk the list of raw lightmaps */ + numUsed = 0; + numTwins = 0; + numTwinLuxels = 0; + numSolidLightmaps = 0; + for ( i = 0; i < numRawLightmaps; i++ ) + { + /* get lightmap */ + lm = &rawLightmaps[ i ]; + + /* walk individual lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early outs */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + continue; + } + + /* allocate bsp luxel storage */ + if ( lm->bspLuxels[ lightmapNum ] == NULL ) { + size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof( float ); + lm->bspLuxels[ lightmapNum ] = safe_malloc( size ); + memset( lm->bspLuxels[ lightmapNum ], 0, size ); + } + + /* allocate radiosity lightmap storage */ + if ( bounce ) { + size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof( float ); + if ( lm->radLuxels[ lightmapNum ] == NULL ) { + lm->radLuxels[ lightmapNum ] = safe_malloc( size ); + } + memset( lm->radLuxels[ lightmapNum ], 0, size ); + } + + /* average supersampled luxels */ + for ( y = 0; y < lm->h; y++ ) + { + for ( x = 0; x < lm->w; x++ ) + { + /* subsample */ + samples = 0.0f; + occludedSamples = 0.0f; + mappedSamples = 0; + VectorClear( sample ); + VectorClear( occludedSample ); + VectorClear( dirSample ); + for ( ly = 0; ly < superSample; ly++ ) + { + for ( lx = 0; lx < superSample; lx++ ) + { + /* sample luxel */ + sx = x * superSample + lx; + sy = y * superSample + ly; + luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + deluxel = SUPER_DELUXEL( sx, sy ); + normal = SUPER_NORMAL( sx, sy ); + cluster = SUPER_CLUSTER( sx, sy ); + + /* sample deluxemap */ + if ( deluxemap && lightmapNum == 0 ) { + VectorAdd( dirSample, deluxel, dirSample ); + } + + /* keep track of used/occluded samples */ + if ( *cluster != CLUSTER_UNMAPPED ) { + mappedSamples++; + } + + /* handle lightmap border? */ + if ( lightmapBorder && ( sx == 0 || sx == ( lm->sw - 1 ) || sy == 0 || sy == ( lm->sh - 1 ) ) && luxel[ 3 ] > 0.0f ) { + VectorSet( sample, 255.0f, 0.0f, 0.0f ); + samples += 1.0f; + } + + /* handle debug */ + else if ( debug && *cluster < 0 ) { + if ( *cluster == CLUSTER_UNMAPPED ) { + VectorSet( luxel, 255, 204, 0 ); + } + else if ( *cluster == CLUSTER_OCCLUDED ) { + VectorSet( luxel, 255, 0, 255 ); + } + else if ( *cluster == CLUSTER_FLOODED ) { + VectorSet( luxel, 0, 32, 255 ); + } + VectorAdd( occludedSample, luxel, occludedSample ); + occludedSamples += 1.0f; + } + + /* normal luxel handling */ + else if ( luxel[ 3 ] > 0.0f ) { + /* handle lit or flooded luxels */ + if ( *cluster > 0 || *cluster == CLUSTER_FLOODED ) { + VectorAdd( sample, luxel, sample ); + samples += luxel[ 3 ]; + } + + /* handle occluded or unmapped luxels */ + else + { + VectorAdd( occludedSample, luxel, occludedSample ); + occludedSamples += luxel[ 3 ]; + } + + /* handle style debugging */ + if ( debug && lightmapNum > 0 && x < 2 && y < 2 ) { + VectorCopy( debugColors[ 0 ], sample ); + samples = 1; + } + } + } + } + + /* only use occluded samples if necessary */ + if ( samples <= 0.0f ) { + VectorCopy( occludedSample, sample ); + samples = occludedSamples; + } + + /* get luxels */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* store light direction */ + if ( deluxemap && lightmapNum == 0 ) { + VectorCopy( dirSample, deluxel ); + } + + /* store the sample back in super luxels */ + if ( samples > 0.01f ) { + VectorScale( sample, ( 1.0f / samples ), luxel ); + luxel[ 3 ] = 1.0f; + } + + /* if any samples were mapped in any way, store ambient color */ + else if ( mappedSamples > 0 ) { + if ( lightmapNum == 0 ) { + VectorCopy( ambientColor, luxel ); + } + else{ + VectorClear( luxel ); + } + luxel[ 3 ] = 1.0f; + } + + /* store a bogus value to be fixed later */ + else + { + VectorClear( luxel ); + luxel[ 3 ] = -1.0f; + } + } + } + + /* setup */ + lm->used = 0; + ClearBounds( colorMins, colorMaxs ); + + /* clean up and store into bsp luxels */ + for ( y = 0; y < lm->h; y++ ) + { + for ( x = 0; x < lm->w; x++ ) + { + /* get luxels */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* copy light direction */ + if ( deluxemap && lightmapNum == 0 ) { + VectorCopy( deluxel, dirSample ); + } + + /* is this a valid sample? */ + if ( luxel[ 3 ] > 0.0f ) { + VectorCopy( luxel, sample ); + samples = luxel[ 3 ]; + numUsed++; + lm->used++; + + /* fix negative samples */ + for ( j = 0; j < 3; j++ ) + { + if ( sample[ j ] < 0.0f ) { + sample[ j ] = 0.0f; + } + } + } + else + { + /* nick an average value from the neighbors */ + VectorClear( sample ); + VectorClear( dirSample ); + samples = 0.0f; + + /* fixme: why is this disabled?? */ + for ( sy = ( y - 1 ); sy <= ( y + 1 ); sy++ ) + { + if ( sy < 0 || sy >= lm->h ) { + continue; + } + + for ( sx = ( x - 1 ); sx <= ( x + 1 ); sx++ ) + { + if ( sx < 0 || sx >= lm->w || ( sx == x && sy == y ) ) { + continue; + } + + /* get neighbor's particulars */ + luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + if ( luxel[ 3 ] < 0.0f ) { + continue; + } + VectorAdd( sample, luxel, sample ); + samples += luxel[ 3 ]; + } + } + + /* no samples? */ + if ( samples == 0.0f ) { + VectorSet( sample, -1.0f, -1.0f, -1.0f ); + samples = 1.0f; + } + else + { + numUsed++; + lm->used++; + + /* fix negative samples */ + for ( j = 0; j < 3; j++ ) + { + if ( sample[ j ] < 0.0f ) { + sample[ j ] = 0.0f; + } + } + } + } + + /* scale the sample */ + VectorScale( sample, ( 1.0f / samples ), sample ); + + /* store the sample in the radiosity luxels */ + if ( bounce > 0 ) { + radLuxel = RAD_LUXEL( lightmapNum, x, y ); + VectorCopy( sample, radLuxel ); + + /* if only storing bounced light, early out here */ + if ( bounceOnly && !bouncing ) { + continue; + } + } + + /* store the sample in the bsp luxels */ + bspLuxel = BSP_LUXEL( lightmapNum, x, y ); + bspDeluxel = BSP_DELUXEL( x, y ); + + VectorAdd( bspLuxel, sample, bspLuxel ); + if ( deluxemap && lightmapNum == 0 ) { + VectorAdd( bspDeluxel, dirSample, bspDeluxel ); + } + + /* add color to bounds for solid checking */ + if ( samples > 0.0f ) { + AddPointToBounds( bspLuxel, colorMins, colorMaxs ); + } + } + } + + /* set solid color */ + lm->solid[ lightmapNum ] = qfalse; + VectorAdd( colorMins, colorMaxs, lm->solidColor[ lightmapNum ] ); + VectorScale( lm->solidColor[ lightmapNum ], 0.5f, lm->solidColor[ lightmapNum ] ); + + /* nocollapse prevents solid lightmaps */ + if ( noCollapse == qfalse ) { + /* check solid color */ + VectorSubtract( colorMaxs, colorMins, sample ); + if ( ( sample[ 0 ] <= SOLID_EPSILON && sample[ 1 ] <= SOLID_EPSILON && sample[ 2 ] <= SOLID_EPSILON ) || + ( lm->w <= 2 && lm->h <= 2 ) ) { /* small lightmaps get forced to solid color */ + /* set to solid */ + VectorCopy( colorMins, lm->solidColor[ lightmapNum ] ); + lm->solid[ lightmapNum ] = qtrue; + numSolidLightmaps++; + } + + /* if all lightmaps aren't solid, then none of them are solid */ + if ( lm->solid[ lightmapNum ] != lm->solid[ 0 ] ) { + for ( y = 0; y < MAX_LIGHTMAPS; y++ ) + { + if ( lm->solid[ y ] ) { + numSolidLightmaps--; + } + lm->solid[ y ] = qfalse; + } + } + } + + /* wrap bsp luxels if necessary */ + if ( lm->wrap[ 0 ] ) { + for ( y = 0; y < lm->h; y++ ) + { + bspLuxel = BSP_LUXEL( lightmapNum, 0, y ); + bspLuxel2 = BSP_LUXEL( lightmapNum, lm->w - 1, y ); + VectorAdd( bspLuxel, bspLuxel2, bspLuxel ); + VectorScale( bspLuxel, 0.5f, bspLuxel ); + VectorCopy( bspLuxel, bspLuxel2 ); + if ( deluxemap && lightmapNum == 0 ) { + bspDeluxel = BSP_DELUXEL( 0, y ); + bspDeluxel2 = BSP_DELUXEL( lm->w - 1, y ); + VectorAdd( bspDeluxel, bspDeluxel2, bspDeluxel ); + VectorScale( bspDeluxel, 0.5f, bspDeluxel ); + VectorCopy( bspDeluxel, bspDeluxel2 ); + } + } + } + if ( lm->wrap[ 1 ] ) { + for ( x = 0; x < lm->w; x++ ) + { + bspLuxel = BSP_LUXEL( lightmapNum, x, 0 ); + bspLuxel2 = BSP_LUXEL( lightmapNum, x, lm->h - 1 ); + VectorAdd( bspLuxel, bspLuxel2, bspLuxel ); + VectorScale( bspLuxel, 0.5f, bspLuxel ); + VectorCopy( bspLuxel, bspLuxel2 ); + if ( deluxemap && lightmapNum == 0 ) { + bspDeluxel = BSP_DELUXEL( x, 0 ); + bspDeluxel2 = BSP_DELUXEL( x, lm->h - 1 ); + VectorAdd( bspDeluxel, bspDeluxel2, bspDeluxel ); + VectorScale( bspDeluxel, 0.5f, bspDeluxel ); + VectorCopy( bspDeluxel, bspDeluxel2 ); + } + } + } + } + } + + /* ----------------------------------------------------------------- + convert modelspace deluxemaps to tangentspace + ----------------------------------------------------------------- */ + /* note it */ + if ( !bouncing ) { + if ( deluxemap && deluxemode == 1 ) { + vec3_t worldUp, myNormal, myTangent, myBinormal; + float dist; + + Sys_Printf( "converting..." ); + + for ( i = 0; i < numRawLightmaps; i++ ) + { + /* get lightmap */ + lm = &rawLightmaps[ i ]; + + /* walk lightmap samples */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get normal and deluxel */ + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + bspDeluxel = BSP_DELUXEL( x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* get normal */ + VectorSet( myNormal, normal[0], normal[1], normal[2] ); + + /* get tangent vectors */ + if ( myNormal[ 0 ] == 0.0f && myNormal[ 1 ] == 0.0f ) { + if ( myNormal[ 2 ] == 1.0f ) { + VectorSet( myTangent, 1.0f, 0.0f, 0.0f ); + VectorSet( myBinormal, 0.0f, 1.0f, 0.0f ); + } + else if ( myNormal[ 2 ] == -1.0f ) { + VectorSet( myTangent, -1.0f, 0.0f, 0.0f ); + VectorSet( myBinormal, 0.0f, 1.0f, 0.0f ); + } + } + else + { + VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); + CrossProduct( myNormal, worldUp, myTangent ); + VectorNormalize( myTangent, myTangent ); + CrossProduct( myTangent, myNormal, myBinormal ); + VectorNormalize( myBinormal, myBinormal ); + } + + /* project onto plane */ + dist = -DotProduct( myTangent, myNormal ); + VectorMA( myTangent, dist, myNormal, myTangent ); + dist = -DotProduct( myBinormal, myNormal ); + VectorMA( myBinormal, dist, myNormal, myBinormal ); + + /* renormalize */ + VectorNormalize( myTangent, myTangent ); + VectorNormalize( myBinormal, myBinormal ); + + /* convert modelspace deluxel to tangentspace */ + dirSample[0] = bspDeluxel[0]; + dirSample[1] = bspDeluxel[1]; + dirSample[2] = bspDeluxel[2]; + VectorNormalize( dirSample, dirSample ); + + /* fix tangents to world matrix */ + if ( myNormal[0] > 0 || myNormal[1] < 0 || myNormal[2] < 0 ) { + VectorNegate( myTangent, myTangent ); + } + + /* build tangentspace vectors */ + bspDeluxel[0] = DotProduct( dirSample, myTangent ); + bspDeluxel[1] = DotProduct( dirSample, myBinormal ); + bspDeluxel[2] = DotProduct( dirSample, myNormal ); + } + } + } + } + } + + /* ----------------------------------------------------------------- + blend lightmaps + ----------------------------------------------------------------- */ + +#ifdef sdfsdfwq312323 + /* note it */ + Sys_Printf( "blending..." ); + + for ( i = 0; i < numRawLightmaps; i++ ) + { + vec3_t myColor; + float myBrightness; + + /* get lightmap */ + lm = &rawLightmaps[ i ]; + + /* walk individual lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early outs */ + if ( lm->superLuxels[ lightmapNum ] == NULL ) { + continue; + } + + /* walk lightmap samples */ + for ( y = 0; y < lm->sh; y++ ) + { + for ( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + bspLuxel = BSP_LUXEL( lightmapNum, x, y ); + + /* get color */ + VectorNormalize( bspLuxel, myColor ); + myBrightness = VectorLength( bspLuxel ); + myBrightness *= ( 1 / 127.0f ); + myBrightness = myBrightness * myBrightness; + myBrightness *= 127.0f; + VectorScale( myColor, myBrightness, bspLuxel ); + } + } + } + } +#endif + + /* ----------------------------------------------------------------- + collapse non-unique lightmaps + ----------------------------------------------------------------- */ + + if ( noCollapse == qfalse && deluxemap == qfalse ) { + /* note it */ + Sys_FPrintf( SYS_VRB, "collapsing..." ); + + /* set all twin refs to null */ + for ( i = 0; i < numRawLightmaps; i++ ) + { + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + rawLightmaps[ i ].twins[ lightmapNum ] = NULL; + rawLightmaps[ i ].twinNums[ lightmapNum ] = -1; + rawLightmaps[ i ].numStyledTwins = 0; + } + } + + /* walk the list of raw lightmaps */ + for ( i = 0; i < numRawLightmaps; i++ ) + { + /* get lightmap */ + lm = &rawLightmaps[ i ]; + + /* walk lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early outs */ + if ( lm->bspLuxels[ lightmapNum ] == NULL || + lm->twins[ lightmapNum ] != NULL ) { + continue; + } + + /* find all lightmaps that are virtually identical to this one */ + for ( j = i + 1; j < numRawLightmaps; j++ ) + { + /* get lightmap */ + lm2 = &rawLightmaps[ j ]; + + /* walk lightmaps */ + for ( lightmapNum2 = 0; lightmapNum2 < MAX_LIGHTMAPS; lightmapNum2++ ) + { + /* early outs */ + if ( lm2->bspLuxels[ lightmapNum2 ] == NULL || + lm2->twins[ lightmapNum2 ] != NULL ) { + continue; + } + + /* compare them */ + if ( CompareBSPLuxels( lm, lightmapNum, lm2, lightmapNum2 ) ) { + /* merge and set twin */ + if ( MergeBSPLuxels( lm, lightmapNum, lm2, lightmapNum2 ) ) { + lm2->twins[ lightmapNum2 ] = lm; + lm2->twinNums[ lightmapNum2 ] = lightmapNum; + numTwins++; + numTwinLuxels += ( lm->w * lm->h ); + + /* count styled twins */ + if ( lightmapNum > 0 ) { + lm->numStyledTwins++; + } + } + } + } + } + } + } + } + + /* ----------------------------------------------------------------- + sort raw lightmaps by shader + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "sorting..." ); + + /* allocate a new sorted list */ + if ( sortLightmaps == NULL ) { + sortLightmaps = safe_malloc( numRawLightmaps * sizeof( int ) ); + } + + /* fill it out and sort it */ + for ( i = 0; i < numRawLightmaps; i++ ) + sortLightmaps[ i ] = i; + qsort( sortLightmaps, numRawLightmaps, sizeof( int ), CompareRawLightmap ); + + /* ----------------------------------------------------------------- + allocate output lightmaps + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "allocating..." ); + + /* kill all existing output lightmaps */ + if ( outLightmaps != NULL ) { + for ( i = 0; i < numOutLightmaps; i++ ) + { + free( outLightmaps[ i ].lightBits ); +#ifdef LIGHTMAP_HDR + free( outLightmaps[ i ].bspLightHDR ); +#else + free( outLightmaps[ i ].bspLightBytes ); +#endif + } + free( outLightmaps ); + outLightmaps = NULL; + } + + numLightmapShaders = 0; + numOutLightmaps = 0; + numBSPLightmaps = 0; + numExtLightmaps = 0; + + /* find output lightmap */ + for ( i = 0; i < numRawLightmaps; i++ ) + { + lm = &rawLightmaps[ sortLightmaps[ i ] ]; + FindOutLightmaps( lm, fastAllocate ); + } + + /* set output numbers in twinned lightmaps */ + for ( i = 0; i < numRawLightmaps; i++ ) + { + /* get lightmap */ + lm = &rawLightmaps[ sortLightmaps[ i ] ]; + + /* walk lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* get twin */ + lm2 = lm->twins[ lightmapNum ]; + if ( lm2 == NULL ) { + continue; + } + lightmapNum2 = lm->twinNums[ lightmapNum ]; + + /* find output lightmap from twin */ + lm->outLightmapNums[ lightmapNum ] = lm2->outLightmapNums[ lightmapNum2 ]; + lm->lightmapX[ lightmapNum ] = lm2->lightmapX[ lightmapNum2 ]; + lm->lightmapY[ lightmapNum ] = lm2->lightmapY[ lightmapNum2 ]; + } + } + + /* ----------------------------------------------------------------- + store output lightmaps + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "storing..." ); + + /* count the bsp lightmaps and allocate space */ + if ( bspLightBytes != NULL ) { + free( bspLightBytes ); + } + if ( numBSPLightmaps == 0 || externalLightmaps ) { + numBSPLightBytes = 0; + bspLightBytes = NULL; + } + else + { + numBSPLightBytes = ( numBSPLightmaps * game->lightmapSize * game->lightmapSize * 3 ); + bspLightBytes = safe_malloc( numBSPLightBytes ); + memset( bspLightBytes, 0, numBSPLightBytes ); + } + + /* walk the list of output lightmaps */ + for ( i = 0; i < numOutLightmaps; i++ ) + { + /* get output lightmap */ + olm = &outLightmaps[ i ]; + + /* fill output lightmap */ + if ( lightmapFill ) { + FillOutLightmap( olm ); + } + + /* is this a valid bsp lightmap? */ + if ( olm->lightmapNum >= 0 && !externalLightmaps ) { + /* copy lighting data */ +#ifdef LIGHTMAP_HDR + int j; + float *flb = olm->bspLightHDR; + lb = bspLightBytes + ( olm->lightmapNum * game->lightmapSize * game->lightmapSize * 3 ); + for (j = 0; j < game->lightmapSize * game->lightmapSize*3; j+=3) + ColorToBytes(flb+j, lb+j, 1); +#else + lb = bspLightBytes + ( olm->lightmapNum * game->lightmapSize * game->lightmapSize * 3 ); + memcpy( lb, olm->bspLightBytes, game->lightmapSize * game->lightmapSize * 3 ); +#endif + + /* copy direction data */ + if ( deluxemap ) { + lb = bspLightBytes + ( ( olm->lightmapNum + 1 ) * game->lightmapSize * game->lightmapSize * 3 ); + memcpy( lb, olm->bspDirBytes, game->lightmapSize * game->lightmapSize * 3 ); + } + } + + /* external lightmap? */ + if ( olm->lightmapNum < 0 || olm->extLightmapNum >= 0 || externalLightmaps ) { + /* make a directory for the lightmaps */ + Q_mkdir( dirname ); + + /* set external lightmap number */ + olm->extLightmapNum = numExtLightmaps; + + /* write lightmap */ + sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps ); + Sys_FPrintf( SYS_VRB, "\nwriting %s", filename ); +#ifdef LIGHTMAP_HDR + WriteHDR( filename, olm->bspLightHDR, olm->customWidth, olm->customHeight, qtrue, externalHDRLightmaps ); +#else + WriteTGA24( filename, olm->bspLightBytes, olm->customWidth, olm->customHeight, qtrue ); +#endif + numExtLightmaps++; + + /* write deluxemap */ + if ( deluxemap ) { + sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps ); + Sys_FPrintf( SYS_VRB, "\nwriting %s", filename ); + WriteTGA24( filename, olm->bspDirBytes, olm->customWidth, olm->customHeight, qtrue ); + numExtLightmaps++; + + if ( debugDeluxemap ) { + olm->extLightmapNum++; + } + } + } + } + + if ( numExtLightmaps > 0 ) { + Sys_FPrintf( SYS_VRB, "\n" ); + } + + /* delete unused external lightmaps */ + for ( i = numExtLightmaps; i; i++ ) + { + /* determine if file exists */ + sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, i ); + if ( !FileExists( filename ) ) { + break; + } + + /* delete it */ + remove( filename ); + } + + /* ----------------------------------------------------------------- + project the lightmaps onto the bsp surfaces + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "projecting..." ); + + /* walk the list of surfaces */ + for ( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get the surface and info */ + ds = &bspDrawSurfaces[ i ]; + info = &surfaceInfos[ i ]; + lm = info->lm; + olm = NULL; + + /* handle surfaces with identical parent */ + if ( info->parentSurfaceNum >= 0 ) { + /* preserve original data and get parent */ + parent = &bspDrawSurfaces[ info->parentSurfaceNum ]; + memcpy( &dsTemp, ds, sizeof( *ds ) ); + + /* overwrite child with parent data */ + memcpy( ds, parent, sizeof( *ds ) ); + + /* restore key parts */ + ds->fogNum = dsTemp.fogNum; + ds->firstVert = dsTemp.firstVert; + ds->firstIndex = dsTemp.firstIndex; + memcpy( ds->lightmapVecs, dsTemp.lightmapVecs, sizeof( dsTemp.lightmapVecs ) ); + + /* set vertex data */ + dv = &bspDrawVerts[ ds->firstVert ]; + dvParent = &bspDrawVerts[ parent->firstVert ]; + for ( j = 0; j < ds->numVerts; j++ ) + { + memcpy( dv[ j ].lightmap, dvParent[ j ].lightmap, sizeof( dv[ j ].lightmap ) ); + memcpy( dv[ j ].color, dvParent[ j ].color, sizeof( dv[ j ].color ) ); + } + + /* skip the rest */ + continue; + } + + /* handle vertex lit or approximated surfaces */ + else if ( lm == NULL || lm->outLightmapNums[ 0 ] < 0 ) { + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + ds->lightmapNum[ lightmapNum ] = -3; + ds->lightmapStyles[ lightmapNum ] = ds->vertexStyles[ lightmapNum ]; + } + } + + /* handle lightmapped surfaces */ + else + { + /* walk lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* set style */ + ds->lightmapStyles[ lightmapNum ] = lm->styles[ lightmapNum ]; + + /* handle unused style */ + if ( lm->styles[ lightmapNum ] == LS_NONE || lm->outLightmapNums[ lightmapNum ] < 0 ) { + ds->lightmapNum[ lightmapNum ] = -3; + continue; + } + + /* get output lightmap */ + olm = &outLightmaps[ lm->outLightmapNums[ lightmapNum ] ]; + + /* set bsp lightmap number */ + ds->lightmapNum[ lightmapNum ] = olm->lightmapNum; + + /* deluxemap debugging makes the deluxemap visible */ + if ( deluxemap && debugDeluxemap && lightmapNum == 0 ) { + ds->lightmapNum[ lightmapNum ]++; + } + + /* calc lightmap origin in texture space */ + lmx = (float) lm->lightmapX[ lightmapNum ] / (float) olm->customWidth; + lmy = (float) lm->lightmapY[ lightmapNum ] / (float) olm->customHeight; + + /* calc lightmap st coords */ + dv = &bspDrawVerts[ ds->firstVert ]; + ydv = &yDrawVerts[ ds->firstVert ]; + for ( j = 0; j < ds->numVerts; j++ ) + { + if ( lm->solid[ lightmapNum ] ) { + dv[ j ].lightmap[ lightmapNum ][ 0 ] = lmx + ( 0.5f / (float) olm->customWidth ); + dv[ j ].lightmap[ lightmapNum ][ 1 ] = lmy + ( 0.5f / (float) olm->customWidth ); + } + else + { + dv[ j ].lightmap[ lightmapNum ][ 0 ] = lmx + ( ydv[ j ].lightmap[ 0 ][ 0 ] / ( superSample * olm->customWidth ) ); + dv[ j ].lightmap[ lightmapNum ][ 1 ] = lmy + ( ydv[ j ].lightmap[ 0 ][ 1 ] / ( superSample * olm->customHeight ) ); + } + } + } + } + + /* store vertex colors */ + dv = &bspDrawVerts[ ds->firstVert ]; + for ( j = 0; j < ds->numVerts; j++ ) + { + /* walk lightmaps */ + for ( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* handle unused style */ + if ( ds->vertexStyles[ lightmapNum ] == LS_NONE ) { + VectorClear( color ); + } + else + { + /* get vertex color */ + luxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + j ); + VectorCopy( luxel, color ); + + /* set minimum light */ + if ( lightmapNum == 0 ) { + for ( k = 0; k < 3; k++ ) + if ( color[ k ] < minVertexLight[ k ] ) { + color[ k ] = minVertexLight[ k ]; + } + } + } + + /* store to bytes */ + if ( !info->si->noVertexLight ) { + ColorToBytes( color, dv[ j ].color[ lightmapNum ], info->si->vertexScale ); + } + } + } + + /* surfaces with styled lightmaps and a style marker get a custom generated shader (fixme: make this work with external lightmaps) */ + if ( olm != NULL && lm != NULL && lm->styles[ 1 ] != LS_NONE && game->load != LoadRBSPFile ) { //% info->si->styleMarker > 0 ) + qboolean dfEqual; + char key[ 32 ], styleStage[ 512 ], styleStages[ 4096 ], rgbGen[ 128 ], alphaGen[ 128 ]; + + + /* setup */ + sprintf( styleStages, "\n\t// Q3Map2 custom lightstyle stage(s)\n" ); + dv = &bspDrawVerts[ ds->firstVert ]; + + /* depthFunc equal? */ + if ( info->si->styleMarker == 2 || info->si->implicitMap == IM_MASKED ) { + dfEqual = qtrue; + } + else{ + dfEqual = qfalse; + } + + /* generate stages for styled lightmaps */ + for ( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + style = lm->styles[ lightmapNum ]; + if ( style == LS_NONE || lm->outLightmapNums[ lightmapNum ] < 0 ) { + continue; + } + + /* get output lightmap */ + olm = &outLightmaps[ lm->outLightmapNums[ lightmapNum ] ]; + + /* lightmap name */ + if ( lm->outLightmapNums[ lightmapNum ] == lm->outLightmapNums[ 0 ] ) { + strcpy( lightmapName, "$lightmap" ); + } + else{ + sprintf( lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum ); + } + + /* get rgbgen string */ + if ( rgbGenValues[ style ] == NULL ) { + sprintf( key, "_style%drgbgen", style ); + rgbGenValues[ style ] = ValueForKey( &entities[ 0 ], key ); + if ( rgbGenValues[ style ][ 0 ] == '\0' ) { + rgbGenValues[ style ] = "wave noise 0.5 1 0 5.37"; + } + } + rgbGen[ 0 ] = '\0'; + if ( rgbGenValues[ style ][ 0 ] != '\0' ) { + sprintf( rgbGen, "\t\trgbGen %s // style %d\n", rgbGenValues[ style ], style ); + } + else{ + rgbGen[ 0 ] = '\0'; + } + + /* get alphagen string */ + if ( alphaGenValues[ style ] == NULL ) { + sprintf( key, "_style%dalphagen", style ); + alphaGenValues[ style ] = ValueForKey( &entities[ 0 ], key ); + } + if ( alphaGenValues[ style ][ 0 ] != '\0' ) { + sprintf( alphaGen, "\t\talphaGen %s // style %d\n", alphaGenValues[ style ], style ); + } + else{ + alphaGen[ 0 ] = '\0'; + } + + /* calculate st offset */ + lmx = dv[ 0 ].lightmap[ lightmapNum ][ 0 ] - dv[ 0 ].lightmap[ 0 ][ 0 ]; + lmy = dv[ 0 ].lightmap[ lightmapNum ][ 1 ] - dv[ 0 ].lightmap[ 0 ][ 1 ]; + + /* create additional stage */ + if ( lmx == 0.0f && lmy == 0.0f ) { + sprintf( styleStage, "\t{\n" + "\t\tmap %s\n" /* lightmap */ + "\t\tblendFunc GL_SRC_ALPHA GL_ONE\n" + "%s" /* depthFunc equal */ + "%s" /* rgbGen */ + "%s" /* alphaGen */ + "\t\ttcGen lightmap\n" + "\t}\n", + lightmapName, + ( dfEqual ? "\t\tdepthFunc equal\n" : "" ), + rgbGen, + alphaGen ); + } + else + { + sprintf( styleStage, "\t{\n" + "\t\tmap %s\n" /* lightmap */ + "\t\tblendFunc GL_SRC_ALPHA GL_ONE\n" + "%s" /* depthFunc equal */ + "%s" /* rgbGen */ + "%s" /* alphaGen */ + "\t\ttcGen lightmap\n" + "\t\ttcMod transform 1 0 0 1 %1.5f %1.5f\n" /* st offset */ + "\t}\n", + lightmapName, + ( dfEqual ? "\t\tdepthFunc equal\n" : "" ), + rgbGen, + alphaGen, + lmx, lmy ); + + } + + /* concatenate */ + strcat( styleStages, styleStage ); + } + + /* create custom shader */ + if ( info->si->styleMarker == 2 ) { + csi = CustomShader( info->si, "q3map_styleMarker2", styleStages ); + } + else{ + csi = CustomShader( info->si, "q3map_styleMarker", styleStages ); + } + + /* emit remap command */ + //% EmitVertexRemapShader( csi->shader, info->si->shader ); + + /* store it */ + //% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) ); + ds->shaderNum = EmitShader( csi->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags ); + //% Sys_Printf( ")\n" ); + } + + /* devise a custom shader for this surface (fixme: make this work with light styles) */ + else if ( olm != NULL && lm != NULL && !externalLightmaps && + ( olm->customWidth != game->lightmapSize || olm->customHeight != game->lightmapSize ) ) { + /* get output lightmap */ + olm = &outLightmaps[ lm->outLightmapNums[ 0 ] ]; + + /* do some name mangling */ + sprintf( lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum ); + + /* create custom shader */ + csi = CustomShader( info->si, "$lightmap", lightmapName ); + + /* store it */ + //% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) ); + ds->shaderNum = EmitShader( csi->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags ); + //% Sys_Printf( ")\n" ); + } + + /* use the normal plain-jane shader */ + else{ + ds->shaderNum = EmitShader( info->si->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags ); + } + } + + /* finish */ + Sys_FPrintf( SYS_VRB, "done.\n" ); + + /* calc num stored */ + numStored = numBSPLightBytes / 3; + efficiency = ( numStored <= 0 ) + ? 0 + : (float) numUsed / (float) numStored; + + /* print stats */ + Sys_Printf( "%9d luxels used\n", numUsed ); + Sys_Printf( "%9d luxels stored (%3.2f percent efficiency)\n", numStored, efficiency * 100.0f ); + Sys_Printf( "%9d solid surface lightmaps\n", numSolidLightmaps ); + Sys_Printf( "%9d identical surface lightmaps, using %d luxels\n", numTwins, numTwinLuxels ); + Sys_Printf( "%9d vertex forced surfaces\n", numSurfsVertexForced ); + Sys_Printf( "%9d vertex approximated surfaces\n", numSurfsVertexApproximated ); + Sys_Printf( "%9d BSP lightmaps\n", numBSPLightmaps ); + Sys_Printf( "%9d total lightmaps\n", numOutLightmaps ); + Sys_Printf( "%9d unique lightmap/shader combinations\n", numLightmapShaders ); + + /* write map shader file */ + WriteMapShaderFile(); +} diff --git a/tools/vmap/main.c b/tools/vmap/main.c new file mode 100644 index 0000000..c2ed996 --- /dev/null +++ b/tools/vmap/main.c @@ -0,0 +1,270 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define MAIN_C + + + +/* dependencies */ +#include "vmap.h" +#include + +/* + Random() + returns a pseudorandom number between 0 and 1 + */ + +vec_t Random( void ){ + return (vec_t) rand() / RAND_MAX; +} + + +char *Q_strncpyz( char *dst, const char *src, size_t len ) { + if ( len == 0 ) { + abort(); + } + + strncpy( dst, src, len ); + dst[ len - 1 ] = '\0'; + return dst; +} + + +char *Q_strcat( char *dst, size_t dlen, const char *src ) { + size_t n = strlen( dst ); + + if ( n > dlen ) { + abort(); /* buffer overflow */ + } + + return Q_strncpyz( dst + n, src, dlen - n ); +} + + +char *Q_strncat( char *dst, size_t dlen, const char *src, size_t slen ) { + size_t n = strlen( dst ); + + if ( n > dlen ) { + abort(); /* buffer overflow */ + } + + return Q_strncpyz( dst + n, src, MIN( slen, dlen - n ) ); +} + + +/* + ExitQ3Map() + cleanup routine + */ + +static void ExitQ3Map( void ){ + BSPFilesCleanup(); + if ( mapDrawSurfs != NULL ) { + free( mapDrawSurfs ); + } +} + + +/* + main() + q3map mojo... + */ + +int main( int argc, char **argv ){ + int i, r; + double start, end; + + Sys_Printf( "VMAP - v1.0 (c) 2015-2020 Vera Visions, LLC.\n" ); + + /* we want consistent 'randomness' */ + srand( 0 ); + + /* start timer */ + start = I_FloatTime(); + + /* set exit call */ + atexit( ExitQ3Map ); + + /* read general options first */ + for ( i = 1; i < argc; i++ ) + { + /* -help */ + if ( !strcmp( argv[ i ], "-h" ) || !strcmp( argv[ i ], "--help" ) + || !strcmp( argv[ i ], "-help" ) ) { + HelpMain(argv[i+1]); + return 0; + } + + /* -connect */ + if ( !strcmp( argv[ i ], "-connect" ) ) { + argv[ i ] = NULL; + i++; + Broadcast_Setup( argv[ i ] ); + argv[ i ] = NULL; + } + + /* verbose */ + else if ( !strcmp( argv[ i ], "-v" ) ) { + if ( !verbose ) { + verbose = qtrue; + argv[ i ] = NULL; + } + } + + /* force */ + else if ( !strcmp( argv[ i ], "-force" ) ) { + force = qtrue; + argv[ i ] = NULL; + } + + /* patch subdivisions */ + else if ( !strcmp( argv[ i ], "-subdivisions" ) ) { + argv[ i ] = NULL; + i++; + patchSubdivisions = atoi( argv[ i ] ); + argv[ i ] = NULL; + if ( patchSubdivisions <= 0 ) { + patchSubdivisions = 1; + } + } + + /* threads */ + else if ( !strcmp( argv[ i ], "-threads" ) ) { + argv[ i ] = NULL; + i++; + numthreads = atoi( argv[ i ] ); + argv[ i ] = NULL; + } + } + + /* init model library */ + PicoInit(); + PicoSetMallocFunc( safe_malloc ); + PicoSetFreeFunc( free ); + PicoSetPrintFunc( PicoPrintFunc ); + PicoSetLoadFileFunc( PicoLoadFileFunc ); + PicoSetFreeFileFunc( free ); + + /* set number of threads */ + ThreadSetDefault(); + + /* generate sinusoid jitter table */ + for ( i = 0; i < MAX_JITTERS; i++ ) + { + jitters[ i ] = sin( i * 139.54152147 ); + //% Sys_Printf( "Jitter %4d: %f\n", i, jitters[ i ] ); + } + + /* ydnar: new path initialization */ + InitPaths( &argc, argv ); + + /* set game options */ + if ( !patchSubdivisions ) { + patchSubdivisions = game->patchSubdivisions; + } + + /* check if we have enough options left to attempt something */ + if ( argc < 2 ) { + Error( "Usage: %s [general options] [options] mapfile", argv[ 0 ] ); + } + + /* fixaas */ + if ( !strcmp( argv[ 1 ], "-fixaas" ) ) { + r = FixAASMain( argc - 1, argv + 1 ); + } + + /* analyze */ + else if ( !strcmp( argv[ 1 ], "-analyze" ) ) { + r = AnalyzeBSPMain( argc - 1, argv + 1 ); + } + + /* info */ + else if ( !strcmp( argv[ 1 ], "-info" ) ) { + r = BSPInfoMain( argc - 2, argv + 2 ); + } + + /* vis */ + else if ( !strcmp( argv[ 1 ], "-vis" ) ) { + r = VisMain( argc - 1, argv + 1 ); + } + + /* light */ + else if ( !strcmp( argv[ 1 ], "-light" ) ) { + r = LightMain( argc - 1, argv + 1 ); + } + + /* vlight */ + else if ( !strcmp( argv[ 1 ], "-vlight" ) ) { + Sys_FPrintf( SYS_WRN, "WARNING: VLight is no longer supported, defaulting to -light -fast instead\n\n" ); + argv[ 1 ] = "-fast"; /* eek a hack */ + r = LightMain( argc, argv ); + } + + /* QBall: export entities */ + else if ( !strcmp( argv[ 1 ], "-exportents" ) ) { + r = ExportEntitiesMain( argc - 1, argv + 1 ); + } + + /* ydnar: lightmap export */ + else if ( !strcmp( argv[ 1 ], "-export" ) ) { + r = ExportLightmapsMain( argc - 1, argv + 1 ); + } + + /* ydnar: lightmap import */ + else if ( !strcmp( argv[ 1 ], "-import" ) ) { + r = ImportLightmapsMain( argc - 1, argv + 1 ); + } + + /* ydnar: bsp scaling */ + else if ( !strcmp( argv[ 1 ], "-scale" ) ) { + r = ScaleBSPMain( argc - 1, argv + 1 ); + } + + /* ydnar: bsp conversion */ + else if ( !strcmp( argv[ 1 ], "-convert" ) ) { + r = ConvertBSPMain( argc - 1, argv + 1 ); + } + + /* ydnar: otherwise create a bsp */ + else{ + r = BSPMain( argc, argv ); + } + + /* emit time */ + end = I_FloatTime(); + Sys_Printf( "%9.0f seconds elapsed\n", end - start ); + + /* shut down connection */ + Broadcast_Shutdown(); + + /* return any error code */ + return r; +} diff --git a/tools/vmap/map.c b/tools/vmap/map.c new file mode 100644 index 0000000..4714cb3 --- /dev/null +++ b/tools/vmap/map.c @@ -0,0 +1,2025 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define MAP_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* FIXME: remove these vars */ + +/* undefine to make plane finding use linear sort (note: really slow) */ +#define USE_HASHING +#define PLANE_HASHES 8192 + +int planehash[ PLANE_HASHES ]; + +int c_boxbevels; +int c_edgebevels; +int c_areaportals; +int c_detail; +int c_structural; + + + +/* + PlaneEqual() + ydnar: replaced with variable epsilon for djbob + */ + +qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ){ + float ne, de; + + + /* get local copies */ + ne = normalEpsilon; + de = distanceEpsilon; + + /* compare */ + // We check equality of each component since we're using '<', not '<=' + // (the epsilons may be zero). We want to use '<' intead of '<=' to be + // consistent with the true meaning of "epsilon", and also because other + // parts of the code uses this inequality. + if ( ( p->dist == dist || fabs( p->dist - dist ) < de ) && + ( p->normal[0] == normal[0] || fabs( p->normal[0] - normal[0] ) < ne ) && + ( p->normal[1] == normal[1] || fabs( p->normal[1] - normal[1] ) < ne ) && + ( p->normal[2] == normal[2] || fabs( p->normal[2] - normal[2] ) < ne ) ) { + return qtrue; + } + + /* different */ + return qfalse; +} + + + +/* + AddPlaneToHash() + */ + +void AddPlaneToHash( plane_t *p ){ + int hash; + + + hash = ( PLANE_HASHES - 1 ) & (int) fabs( p->dist ); + + p->hash_chain = planehash[hash]; + planehash[hash] = p - mapplanes + 1; +} + +/* + ================ + CreateNewFloatPlane + ================ + */ +int CreateNewFloatPlane( vec3_t normal, vec_t dist ){ + plane_t *p, temp; + + if ( VectorLength( normal ) < 0.5 ) { + Sys_Printf( "FloatPlane: bad normal\n" ); + return -1; + } + + // create a new plane + AUTOEXPAND_BY_REALLOC( mapplanes, nummapplanes + 1, allocatedmapplanes, 1024 ); + + p = &mapplanes[nummapplanes]; + VectorCopy( normal, p->normal ); + p->dist = dist; + p->type = ( p + 1 )->type = PlaneTypeForNormal( p->normal ); + + VectorSubtract( vec3_origin, normal, ( p + 1 )->normal ); + ( p + 1 )->dist = -dist; + + nummapplanes += 2; + + // allways put axial planes facing positive first + if ( p->type < 3 ) { + if ( p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0 ) { + // flip order + temp = *p; + *p = *( p + 1 ); + *( p + 1 ) = temp; + + AddPlaneToHash( p ); + AddPlaneToHash( p + 1 ); + return nummapplanes - 1; + } + } + + AddPlaneToHash( p ); + AddPlaneToHash( p + 1 ); + return nummapplanes - 2; +} + + + +/* + SnapNormal() + Snaps a near-axial normal vector. + Returns qtrue if and only if the normal was adjusted. + */ + +qboolean SnapNormal( vec3_t normal ){ +#if Q3MAP2_EXPERIMENTAL_SNAP_NORMAL_FIX + int i; + qboolean adjusted = qfalse; + + // A change from the original SnapNormal() is that we snap each + // component that's close to 0. So for example if a normal is + // (0.707, 0.707, 0.0000001), it will get snapped to lie perfectly in the + // XY plane (its Z component will be set to 0 and its length will be + // normalized). The original SnapNormal() didn't snap such vectors - it + // only snapped vectors that were near a perfect axis. + + for ( i = 0; i < 3; i++ ) + { + if ( normal[i] != 0.0 && -normalEpsilon < normal[i] && normal[i] < normalEpsilon ) { + normal[i] = 0.0; + adjusted = qtrue; + } + } + + if ( adjusted ) { + VectorNormalize( normal, normal ); + return qtrue; + } + return qfalse; +#else + int i; + + // I would suggest that you uncomment the following code and look at the + // results: + + /* + Sys_Printf("normalEpsilon is %f\n", normalEpsilon); + for (i = 0;; i++) + { + normal[0] = 1.0; + normal[1] = 0.0; + normal[2] = i * 0.000001; + VectorNormalize(normal, normal); + if (1.0 - normal[0] >= normalEpsilon) { + Sys_Printf("(%f %f %f)\n", normal[0], normal[1], normal[2]); + Error("SnapNormal: test completed"); + } + } + */ + + // When the normalEpsilon is 0.00001, the loop will break out when normal is + // (0.999990 0.000000 0.004469). In other words, this is the vector closest + // to axial that will NOT be snapped. Anything closer will be snaped. Now, + // 0.004469 is close to 1/225. The length of a circular quarter-arc of radius + // 1 is PI/2, or about 1.57. And 0.004469/1.57 is about 0.0028, or about + // 1/350. Expressed a different way, 1/350 is also about 0.26/90. + // This means is that a normal with an angle that is within 1/4 of a degree + // from axial will be "snapped". My belief is that the person who wrote the + // code below did not intend it this way. I think the person intended that + // the epsilon be measured against the vector components close to 0, not 1.0. + // I think the logic should be: if 2 of the normal components are within + // epsilon of 0, then the vector can be snapped to be perfectly axial. + // We may consider adjusting the epsilon to a larger value when we make this + // code fix. + + for ( i = 0; i < 3; i++ ) + { + if ( fabs( normal[ i ] - 1 ) < normalEpsilon ) { + VectorClear( normal ); + normal[ i ] = 1; + return qtrue; + } + if ( fabs( normal[ i ] - -1 ) < normalEpsilon ) { + VectorClear( normal ); + normal[ i ] = -1; + return qtrue; + } + } + return qfalse; +#endif +} + + + +/* + SnapPlane() + snaps a plane to normal/distance epsilons + */ + +void SnapPlane( vec3_t normal, vec_t *dist, vec3_t center ){ +// SnapPlane disabled by LordHavoc because it often messes up collision +// brushes made from triangles of embedded models, and it has little effect +// on anything else (axial planes are usually derived from snapped points) +/* + SnapPlane reenabled by namespace because of multiple reports of + q3map2-crashes which were triggered by this patch. + */ + SnapNormal( normal ); + + // TODO: Rambetter has some serious comments here as well. First off, + // in the case where a normal is non-axial, there is nothing special + // about integer distances. I would think that snapping a distance might + // make sense for axial normals, but I'm not so sure about snapping + // non-axial normals. A shift by 0.01 in a plane, multiplied by a clipping + // against another plane that is 5 degrees off, and we introduce 0.1 error + // easily. A 0.1 error in a vertex is where problems start to happen, such + // as disappearing triangles. + + // Second, assuming we have snapped the normal above, let's say that the + // plane we just snapped was defined for some points that are actually + // quite far away from normal * dist. Well, snapping the normal in this + // case means that we've just moved those points by potentially many units! + // Therefore, if we are going to snap the normal, we need to know the + // points we're snapping for so that the plane snaps with those points in + // mind (points remain close to the plane). + + // I would like to know exactly which problems SnapPlane() is trying to + // solve so that we can better engineer it (I'm not saying that SnapPlane() + // should be removed altogether). Fix all this snapping code at some point! + + if ( fabs( *dist - Q_rint( *dist ) ) < distanceEpsilon ) { + *dist = Q_rint( *dist ); + } +} + +/* + SnapPlaneImproved() + snaps a plane to normal/distance epsilons, improved code + */ +void SnapPlaneImproved( vec3_t normal, vec_t *dist, int numPoints, const vec3_t *points ){ + int i; + vec3_t center; + vec_t distNearestInt; + + if ( SnapNormal( normal ) ) { + if ( numPoints > 0 ) { + // Adjust the dist so that the provided points don't drift away. + VectorClear( center ); + for ( i = 0; i < numPoints; i++ ) + { + VectorAdd( center, points[i], center ); + } + for ( i = 0; i < 3; i++ ) { center[i] = center[i] / numPoints; } + *dist = DotProduct( normal, center ); + } + } + + if ( VectorIsOnAxis( normal ) ) { + // Only snap distance if the normal is an axis. Otherwise there + // is nothing "natural" about snapping the distance to an integer. + distNearestInt = Q_rint( *dist ); + if ( -distanceEpsilon < *dist - distNearestInt && *dist - distNearestInt < distanceEpsilon ) { + *dist = distNearestInt; + } + } +} + + + +/* + FindFloatPlane() + ydnar: changed to allow a number of test points to be supplied that + must be within an epsilon distance of the plane + */ + +int FindFloatPlane( vec3_t innormal, vec_t dist, int numPoints, vec3_t *points ) // NOTE: this has a side effect on the normal. Good or bad? + +#ifdef USE_HASHING + +{ + int i, j, hash, h; + int pidx; + plane_t *p; + vec_t d; + vec3_t normal; + + VectorCopy( innormal, normal ); +#if Q3MAP2_EXPERIMENTAL_SNAP_PLANE_FIX + SnapPlaneImproved( normal, &dist, numPoints, (const vec3_t *) points ); +#else + SnapPlane( normal, &dist ); +#endif + /* hash the plane */ + hash = ( PLANE_HASHES - 1 ) & (int) fabs( dist ); + + /* search the border bins as well */ + for ( i = -1; i <= 1; i++ ) + { + h = ( hash + i ) & ( PLANE_HASHES - 1 ); + for ( pidx = planehash[ h ] - 1; pidx != -1; pidx = mapplanes[pidx].hash_chain - 1 ) + { + p = &mapplanes[pidx]; + + /* do standard plane compare */ + if ( !PlaneEqual( p, normal, dist ) ) { + continue; + } + + /* ydnar: uncomment the following line for old-style plane finding */ + //% return p - mapplanes; + + /* ydnar: test supplied points against this plane */ + for ( j = 0; j < numPoints; j++ ) + { + // NOTE: When dist approaches 2^16, the resolution of 32 bit floating + // point number is greatly decreased. The distanceEpsilon cannot be + // very small when world coordinates extend to 2^16. Making the + // dot product here in 64 bit land will not really help the situation + // because the error will already be carried in dist. + d = DotProduct( points[ j ], p->normal ) - p->dist; + d = fabs( d ); + if ( d != 0.0 && d >= distanceEpsilon ) { + break; // Point is too far from plane. + } + } + + /* found a matching plane */ + if ( j >= numPoints ) { + return p - mapplanes; + } + } + } + + /* none found, so create a new one */ + return CreateNewFloatPlane( normal, dist ); +} + +#else + +{ + int i; + plane_t *p; + vec3_t normal; + + VectorCopy( innormal, normal ); +#if Q3MAP2_EXPERIMENTAL_SNAP_PLANE_FIX + SnapPlaneImproved( normal, &dist, numPoints, (const vec3_t *) points ); +#else + SnapPlane( normal, &dist ); +#endif + for ( i = 0, p = mapplanes; i < nummapplanes; i++, p++ ) + { + if ( !PlaneEqual( p, normal, dist ) ) { + continue; + } + + /* ydnar: uncomment the following line for old-style plane finding */ + //% return i; + + /* ydnar: test supplied points against this plane */ + for ( j = 0; j < numPoints; j++ ) + { + d = DotProduct( points[ j ], p->normal ) - p->dist; + if ( fabs( d ) > distanceEpsilon ) { + break; + } + } + + /* found a matching plane */ + if ( j >= numPoints ) { + return i; + } + // TODO: Note that the non-USE_HASHING code does not compute epsilons + // for the provided points. It should do that. I think this code + // is unmaintained because nobody sets USE_HASHING to off. + } + + return CreateNewFloatPlane( normal, dist ); +} + +#endif + + + +/* + MapPlaneFromPoints() + takes 3 points and finds the plane they lie in + */ + +int MapPlaneFromPoints( vec3_t *p ){ +#if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES + vec3_accu_t paccu[3]; + vec3_accu_t t1, t2, normalAccu; + vec3_t normal; + vec_t dist; + + VectorCopyRegularToAccu( p[0], paccu[0] ); + VectorCopyRegularToAccu( p[1], paccu[1] ); + VectorCopyRegularToAccu( p[2], paccu[2] ); + + VectorSubtractAccu( paccu[0], paccu[1], t1 ); + VectorSubtractAccu( paccu[2], paccu[1], t2 ); + CrossProductAccu( t1, t2, normalAccu ); + VectorNormalizeAccu( normalAccu, normalAccu ); + // TODO: A 32 bit float for the plane distance isn't enough resolution + // if the plane is 2^16 units away from the origin (the "epsilon" approaches + // 0.01 in that case). + dist = (vec_t) DotProductAccu( paccu[0], normalAccu ); + VectorCopyAccuToRegular( normalAccu, normal ); + + return FindFloatPlane( normal, dist, 3, p ); +#else + vec3_t t1, t2, normal; + vec_t dist; + + + /* calc plane normal */ + VectorSubtract( p[ 0 ], p[ 1 ], t1 ); + VectorSubtract( p[ 2 ], p[ 1 ], t2 ); + CrossProduct( t1, t2, normal ); + VectorNormalize( normal, normal ); + + /* calc plane distance */ + dist = DotProduct( p[ 0 ], normal ); + + /* store the plane */ + return FindFloatPlane( normal, dist, 3, p ); +#endif +} + + + +/* + SetBrushContents() + the content flags and compile flags on all sides of a brush should be the same + */ + +void SetBrushContents( brush_t *b ){ + int contentFlags, compileFlags; + side_t *s; + int i; + //% qboolean mixed; + + + /* get initial compile flags from first side */ + s = &b->sides[ 0 ]; + contentFlags = s->contentFlags; + compileFlags = s->compileFlags; + b->contentShader = s->shaderInfo; + //% mixed = qfalse; + + /* get the content/compile flags for every side in the brush */ + for ( i = 1; i < b->numsides; i++, s++ ) + { + s = &b->sides[ i ]; + if ( s->shaderInfo == NULL ) { + continue; + } + //% if( s->contentFlags != contentFlags || s->compileFlags != compileFlags ) + //% mixed = qtrue; + + contentFlags |= s->contentFlags; + compileFlags |= s->compileFlags; + } + + /* ydnar: getting rid of this stupid warning */ + //% if( mixed ) + //% Sys_FPrintf( SYS_VRB,"Entity %i, Brush %i: mixed face contentFlags\n", b->entitynum, b->brushnum ); + + /* check for detail & structural */ + if ( ( compileFlags & C_DETAIL ) && ( compileFlags & C_STRUCTURAL ) ) { + xml_Select( "Mixed detail and structural (defaulting to structural)", mapEnt->mapEntityNum, entitySourceBrushes, qfalse ); + compileFlags &= ~C_DETAIL; + } + + /* the fulldetail flag will cause detail brushes to be treated like normal brushes */ + if ( fulldetail ) { + compileFlags &= ~C_DETAIL; + } + + /* all translucent brushes that aren't specifically made structural will be detail */ + if ( ( compileFlags & C_TRANSLUCENT ) && !( compileFlags & C_STRUCTURAL ) ) { + compileFlags |= C_DETAIL; + } + + /* detail? */ + if ( compileFlags & C_DETAIL ) { + c_detail++; + b->detail = qtrue; + } + else + { + c_structural++; + b->detail = qfalse; + } + + /* opaque? */ + if ( compileFlags & C_TRANSLUCENT ) { + b->opaque = qfalse; + } + else{ + b->opaque = qtrue; + } + + /* areaportal? */ + if ( compileFlags & C_AREAPORTAL ) { + c_areaportals++; + } + + /* set brush flags */ + b->contentFlags = contentFlags; + b->compileFlags = compileFlags; +} + + + +/* + AddBrushBevels() + adds any additional planes necessary to allow the brush being + built to be expanded against axial bounding boxes + ydnar 2003-01-20: added mrelusive fixes + */ + +void AddBrushBevels( void ){ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + side_t *s, *s2; + winding_t *w, *w2; + vec3_t normal; + float dist; + vec3_t vec, vec2; + float d, minBack; + + // + // add the axial planes + // + order = 0; + for ( axis = 0; axis < 3; axis++ ) { + for ( dir = -1; dir <= 1; dir += 2, order++ ) { + // see if the plane is allready present + for ( i = 0, s = buildBrush->sides; i < buildBrush->numsides; i++, s++ ) + { + /* ydnar: testing disabling of mre code */ + #if 0 + if ( dir > 0 ) { + if ( mapplanes[s->planenum].normal[axis] >= 0.9999f ) { + break; + } + } + else { + if ( mapplanes[s->planenum].normal[axis] <= -0.9999f ) { + break; + } + } + #else + if ( ( dir > 0 && mapplanes[ s->planenum ].normal[ axis ] == 1.0f ) || + ( dir < 0 && mapplanes[ s->planenum ].normal[ axis ] == -1.0f ) ) { + break; + } + #endif + } + + if ( i == buildBrush->numsides ) { + // add a new side + if ( buildBrush->numsides == MAX_BUILD_SIDES ) { + xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue ); + } + memset( s, 0, sizeof( *s ) ); + buildBrush->numsides++; + VectorClear( normal ); + normal[axis] = dir; + + if ( dir == 1 ) { + /* ydnar: adding bevel plane snapping for fewer bsp planes */ + if ( bevelSnap > 0 ) { + dist = floor( buildBrush->maxs[ axis ] / bevelSnap ) * bevelSnap; + } + else{ + dist = buildBrush->maxs[ axis ]; + } + } + else + { + /* ydnar: adding bevel plane snapping for fewer bsp planes */ + if ( bevelSnap > 0 ) { + dist = -ceil( buildBrush->mins[ axis ] / bevelSnap ) * bevelSnap; + } + else{ + dist = -buildBrush->mins[ axis ]; + } + } + + s->planenum = FindFloatPlane( normal, dist, 0, NULL ); + s->contentFlags = buildBrush->sides[ 0 ].contentFlags; + s->bevel = qtrue; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if ( i != order ) { + sidetemp = buildBrush->sides[order]; + buildBrush->sides[order] = buildBrush->sides[i]; + buildBrush->sides[i] = sidetemp; + } + } + } + + // + // add the edge bevels + // + if ( buildBrush->numsides == 6 ) { + return; // pure axial + } + + // test the non-axial plane edges + for ( i = 6; i < buildBrush->numsides; i++ ) { + s = buildBrush->sides + i; + w = s->winding; + if ( !w ) { + continue; + } + for ( j = 0; j < w->numpoints; j++ ) { + k = ( j + 1 ) % w->numpoints; + VectorSubtract( w->p[j], w->p[k], vec ); + if ( VectorNormalize( vec, vec ) < 0.5f ) { + continue; + } + SnapNormal( vec ); + for ( k = 0; k < 3; k++ ) { + if ( vec[k] == -1.0f || vec[k] == 1.0f || ( vec[k] == 0.0f && vec[( k + 1 ) % 3] == 0.0f ) ) { + break; // axial + } + } + if ( k != 3 ) { + continue; // only test non-axial edges + } + + /* debug code */ + //% Sys_Printf( "-------------\n" ); + + // try the six possible slanted axials from this edge + for ( axis = 0; axis < 3; axis++ ) { + for ( dir = -1; dir <= 1; dir += 2 ) { + // construct a plane + VectorClear( vec2 ); + vec2[axis] = dir; + CrossProduct( vec, vec2, normal ); + if ( VectorNormalize( normal, normal ) < 0.5f ) { + continue; + } + dist = DotProduct( w->p[j], normal ); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for ( k = 0; k < buildBrush->numsides; k++ ) { + + // if this plane has allready been used, skip it + if ( PlaneEqual( &mapplanes[buildBrush->sides[k].planenum], normal, dist ) ) { + break; + } + + w2 = buildBrush->sides[k].winding; + if ( !w2 ) { + continue; + } + minBack = 0.0f; + for ( l = 0; l < w2->numpoints; l++ ) { + d = DotProduct( w2->p[l], normal ) - dist; + if ( d > 0.1f ) { + break; // point in front + } + if ( d < minBack ) { + minBack = d; + } + } + // if some point was at the front + if ( l != w2->numpoints ) { + break; + } + + // if no points at the back then the winding is on the bevel plane + if ( minBack > -0.1f ) { + //% Sys_Printf( "On bevel plane\n" ); + break; + } + } + + if ( k != buildBrush->numsides ) { + continue; // wasn't part of the outer hull + } + + /* debug code */ + //% Sys_Printf( "n = %f %f %f\n", normal[ 0 ], normal[ 1 ], normal[ 2 ] ); + + // add this plane + if ( buildBrush->numsides == MAX_BUILD_SIDES ) { + xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue ); + } + s2 = &buildBrush->sides[buildBrush->numsides]; + buildBrush->numsides++; + memset( s2, 0, sizeof( *s2 ) ); + + s2->planenum = FindFloatPlane( normal, dist, 1, &w->p[ j ] ); + s2->contentFlags = buildBrush->sides[0].contentFlags; + s2->bevel = qtrue; + c_edgebevels++; + } + } + } + } +} + + + +/* + FinishBrush() + produces a final brush based on the buildBrush->sides array + and links it to the current entity + */ + +static void MergeOrigin( entity_t *ent, vec3_t origin ){ + vec3_t adjustment; + char string[128]; + + /* we have not parsed the brush completely yet... */ + GetVectorForKey( ent, "origin", ent->origin ); + + VectorMA( origin, -1, ent->originbrush_origin, adjustment ); + VectorAdd( adjustment, ent->origin, ent->origin ); + VectorCopy( origin, ent->originbrush_origin ); + + sprintf( string, "%f %f %f", ent->origin[0], ent->origin[1], ent->origin[2] ); + SetKeyValue( ent, "origin", string ); +} + +brush_t *FinishBrush( qboolean noCollapseGroups ){ + brush_t *b; + + + /* create windings for sides and bounds for brush */ + if ( !CreateBrushWindings( buildBrush ) ) { + return NULL; + } + + /* origin brushes are removed, but they set the rotation origin for the rest of the brushes in the entity. + after the entire entity is parsed, the planenums and texinfos will be adjusted for the origin brush */ + if ( buildBrush->compileFlags & C_ORIGIN ) { + vec3_t origin; + + Sys_Printf( "Entity %i, Brush %i: origin brush detected\n", + mapEnt->mapEntityNum, entitySourceBrushes ); + + if ( numEntities == 1 ) { + Sys_Printf( "Entity %i, Brush %i: origin brushes not allowed in world\n", + mapEnt->mapEntityNum, entitySourceBrushes ); + return NULL; + } + + VectorAdd( buildBrush->mins, buildBrush->maxs, origin ); + VectorScale( origin, 0.5, origin ); + + MergeOrigin( &entities[ numEntities - 1 ], origin ); + + /* don't keep this brush */ + return NULL; + } + + /* determine if the brush is an area portal */ + if ( buildBrush->compileFlags & C_AREAPORTAL ) { + if ( numEntities != 1 ) { + Sys_Printf( "Entity %i, Brush %i: areaportals only allowed in world\n", numEntities - 1, entitySourceBrushes ); + return NULL; + } + } + + /* add bevel planes */ + if ( !noCollapseGroups ) { + AddBrushBevels(); + } + + /* keep it */ + b = CopyBrush( buildBrush ); + + /* set map entity and brush numbering */ + b->entityNum = mapEnt->mapEntityNum; + b->brushNum = entitySourceBrushes; + + /* set original */ + b->original = b; + + /* link opaque brushes to head of list, translucent brushes to end */ + if ( b->opaque || mapEnt->lastBrush == NULL ) { + b->next = mapEnt->brushes; + mapEnt->brushes = b; + if ( mapEnt->lastBrush == NULL ) { + mapEnt->lastBrush = b; + } + } + else + { + b->next = NULL; + mapEnt->lastBrush->next = b; + mapEnt->lastBrush = b; + } + + /* link colorMod volume brushes to the entity directly */ + if ( b->contentShader != NULL && + b->contentShader->colorMod != NULL && + b->contentShader->colorMod->type == CM_VOLUME ) { + b->nextColorModBrush = mapEnt->colorModBrushes; + mapEnt->colorModBrushes = b; + } + + /* return to sender */ + return b; +} + + + +/* + TextureAxisFromPlane() + determines best orthagonal axis to project a texture onto a wall + (must be identical in radiant!) + */ + +vec3_t baseaxis[18] = +{ + {0,0,1}, {1,0,0}, {0,-1,0}, // floor + {0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling + {1,0,0}, {0,1,0}, {0,0,-1}, // west wall + {-1,0,0}, {0,1,0}, {0,0,-1}, // east wall + {0,1,0}, {1,0,0}, {0,0,-1}, // south wall + {0,-1,0}, {1,0,0}, {0,0,-1} // north wall +}; + +void TextureAxisFromPlane( plane_t *pln, vec3_t xv, vec3_t yv ){ + int bestaxis; + vec_t dot,best; + int i; + + best = 0; + bestaxis = 0; + + for ( i = 0 ; i < 6 ; i++ ) + { + dot = DotProduct( pln->normal, baseaxis[i * 3] ); + if ( dot > best + 0.0001f ) { /* ydnar: bug 637 fix, suggested by jmonroe */ + best = dot; + bestaxis = i; + } + } + + VectorCopy( baseaxis[bestaxis * 3 + 1], xv ); + VectorCopy( baseaxis[bestaxis * 3 + 2], yv ); +} + + + +/* + QuakeTextureVecs() + creates world-to-texture mapping vecs for crappy quake plane arrangements + */ + +static void QuakeTextureVecs( plane_t *plane, vec_t shift[ 2 ], vec_t rotate, vec_t scale[ 2 ], qboolean is220, vec_t mappingVecs[ 2 ][ 4 ] ){ + vec3_t vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + int i, j; + + if (is220) + { + VectorCopy(mappingVecs[0], vecs[0]); + VectorCopy(mappingVecs[1], vecs[1]); + + //rotate is included only for purposes of regenerating the surface vectors if the surface is edited. its not needed when those vectors are explicit anyway. + rotate = 0; + //scale IS needed though. + } + else + TextureAxisFromPlane( plane, vecs[0], vecs[1] ); + + if ( !scale[0] ) { + scale[0] = 1; + } + if ( !scale[1] ) { + scale[1] = 1; + } + + // rotate axis + if ( rotate == 0 ) { + sinv = 0 ; cosv = 1; + } + else if ( rotate == 90 ) { + sinv = 1 ; cosv = 0; + } + else if ( rotate == 180 ) { + sinv = 0 ; cosv = -1; + } + else if ( rotate == 270 ) { + sinv = -1 ; cosv = 0; + } + else + { + ang = rotate / 180 * Q_PI; + sinv = sin( ang ); + cosv = cos( ang ); + } + + if ( vecs[0][0] ) { + sv = 0; + } + else if ( vecs[0][1] ) { + sv = 1; + } + else{ + sv = 2; + } + + if ( vecs[1][0] ) { + tv = 0; + } + else if ( vecs[1][1] ) { + tv = 1; + } + else{ + tv = 2; + } + + for ( i = 0 ; i < 2 ; i++ ) { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for ( i = 0 ; i < 2 ; i++ ) + for ( j = 0 ; j < 3 ; j++ ) + mappingVecs[i][j] = vecs[i][j] / scale[i]; + + mappingVecs[0][3] = shift[0]; + mappingVecs[1][3] = shift[1]; +} + + + +/* + ParseRawBrush() + parses the sides into buildBrush->sides[], nothing else. + no validation, back plane removal, etc. + + Timo - 08/26/99 + added brush epairs parsing ( ignoring actually ) + Timo - 08/04/99 + added exclusive brush primitive parsing + Timo - 08/08/99 + support for old brush format back in + NOTE: it would be "cleaner" to have seperate functions to parse between old and new brushes + */ + +static void ParseRawBrush( qboolean onlyLights ){ + side_t *side; + vec3_t planePoints[ 3 ]; + int planenum; + shaderInfo_t *si; + vec_t shift[ 2 ]; + vec_t rotate = 0; + vec_t scale[ 2 ]; + char name[ MAX_QPATH ]; + char shader[ MAX_QPATH+16 ]; + int flags; + qboolean is220; + + + /* initial setup */ + buildBrush->numsides = 0; + buildBrush->detail = qfalse; + + /* bp */ + if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) { + MatchToken( "{" ); + } + + /* parse sides */ + while ( 1 ) + { + if ( !GetToken( qtrue ) ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + /* ttimo : bp: here we may have to jump over brush epairs (only used in editor) */ + if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) { + while ( 1 ) + { + if ( strcmp( token, "(" ) ) { + GetToken( qfalse ); + } + else{ + break; + } + GetToken( qtrue ); + } + } + UnGetToken(); + + /* test side count */ + if ( buildBrush->numsides >= MAX_BUILD_SIDES ) { + xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue ); + } + + /* add side */ + side = &buildBrush->sides[ buildBrush->numsides ]; + memset( side, 0, sizeof( *side ) ); + buildBrush->numsides++; + + /* read the three point plane definition */ + Parse1DMatrix( 3, planePoints[ 0 ] ); + Parse1DMatrix( 3, planePoints[ 1 ] ); + Parse1DMatrix( 3, planePoints[ 2 ] ); + + /* bp: read the texture matrix */ + if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) { + Parse2DMatrix( 2, 3, (float*) side->texMat ); + } + + /* read shader name */ + GetToken( qfalse ); + strcpy( name, token ); + + is220 = qfalse; + /* bp */ + if ( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) { + GetToken( qfalse ); + if (!strcmp(token, "[")) + { //valve-format + is220 = qtrue; + GetToken( qfalse ); + side->vecs[0][0] = atof(token); + GetToken( qfalse ); + side->vecs[0][1] = atof(token); + GetToken( qfalse ); + side->vecs[0][2] = atof(token); + GetToken( qfalse ); + shift[0] = atof(token); + + GetToken( qfalse ); + if (strcmp(token, "]")) + Error( "ParseRawBrush: found %s, expected %s", "]", token ); + GetToken( qfalse ); + if (strcmp(token, "[")) + Error( "ParseRawBrush: found %s, expected %s", "[", token ); + + GetToken( qfalse ); + side->vecs[1][0] = atof(token); + GetToken( qfalse ); + side->vecs[1][1] = atof(token); + GetToken( qfalse ); + side->vecs[1][2] = atof(token); + GetToken( qfalse ); + shift[1] = atof(token); + + GetToken( qfalse ); + if (strcmp(token, "]")) + Error( "ParseRawBrush: found %s, expected %s", "]", token ); + } + else + { //quake-format + shift[ 0 ] = atof( token ); + GetToken( qfalse ); + shift[ 1 ] = atof( token ); + } + + GetToken( qfalse ); + rotate = atof( token ); + GetToken( qfalse ); + scale[ 0 ] = atof( token ); + GetToken( qfalse ); + scale[ 1 ] = atof( token ); + } + + /* set default flags and values */ + sprintf( shader, "textures/%s", name ); + if ( onlyLights ) { + si = &shaderInfo[ 0 ]; + } + else{ + si = ShaderInfoForShader( shader ); + } + side->shaderInfo = si; + side->surfaceFlags = si->surfaceFlags; + side->contentFlags = si->contentFlags; + side->compileFlags = si->compileFlags; + side->value = si->value; + + /* ydnar: gs mods: bias texture shift */ + if ( si->globalTexture == qfalse ) { + shift[ 0 ] -= ( floor( shift[ 0 ] / si->shaderWidth ) * si->shaderWidth ); + shift[ 1 ] -= ( floor( shift[ 1 ] / si->shaderHeight ) * si->shaderHeight ); + } + + /* + historically, there are 3 integer values at the end of a brushside line in a .map file. + in quake 3, the only thing that mattered was the first of these three values, which + was previously the content flags. and only then did a single bit matter, the detail + bit. because every game has its own special flags for specifying detail, the + traditionally game-specified CONTENTS_DETAIL flag was overridden for Q3Map 2.3.0 + by C_DETAIL, defined in vmap.h. the value is exactly as it was before, but + is stored in compileFlags, as opposed to contentFlags, for multiple-game + portability. :sigh: + */ + + if ( TokenAvailable() ) { + /* get detail bit from map content flags */ + GetToken( qfalse ); + flags = atoi( token ); + if ( flags & C_DETAIL ) { + side->compileFlags |= C_DETAIL; + } + + /* historical */ + GetToken( qfalse ); + //% td.flags = atoi( token ); + GetToken( qfalse ); + //% td.value = atoi( token ); + } + + /* find the plane number */ + planenum = MapPlaneFromPoints( planePoints ); + side->planenum = planenum; + + /* bp: get the texture mapping for this texturedef / plane combination */ + if ( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) { + QuakeTextureVecs( &mapplanes[ planenum ], shift, rotate, scale, is220, side->vecs ); + } + } + + /* bp */ + if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) { + UnGetToken(); + MatchToken( "}" ); + MatchToken( "}" ); + } +} + + + +/* + RemoveDuplicateBrushPlanes + returns false if the brush has a mirrored set of planes, + meaning it encloses no volume. + also removes planes without any normal + */ + +qboolean RemoveDuplicateBrushPlanes( brush_t *b ){ + int i, j, k; + side_t *sides; + + sides = b->sides; + + for ( i = 1 ; i < b->numsides ; i++ ) { + + // check for a degenerate plane + if ( sides[i].planenum == -1 ) { + xml_Select( "degenerate plane", b->entityNum, b->brushNum, qfalse ); + // remove it + for ( k = i + 1 ; k < b->numsides ; k++ ) { + sides[k - 1] = sides[k]; + } + b->numsides--; + i--; + continue; + } + + // check for duplication and mirroring + for ( j = 0 ; j < i ; j++ ) { + if ( sides[i].planenum == sides[j].planenum ) { + xml_Select( "duplicate plane", b->entityNum, b->brushNum, qfalse ); + // remove the second duplicate + for ( k = i + 1 ; k < b->numsides ; k++ ) { + sides[k - 1] = sides[k]; + } + b->numsides--; + i--; + break; + } + + if ( sides[i].planenum == ( sides[j].planenum ^ 1 ) ) { + // mirror plane, brush is invalid + xml_Select( "mirrored plane", b->entityNum, b->brushNum, qfalse ); + return qfalse; + } + } + } + return qtrue; +} + + + +/* + ParseBrush() + parses a brush out of a map file and sets it up + */ + +static void ParseBrush( qboolean onlyLights, qboolean noCollapseGroups ){ + /* parse the brush out of the map */ + ParseRawBrush( onlyLights ); + + /* only go this far? */ + if ( onlyLights ) { + return; + } + + /* set some defaults */ + buildBrush->portalareas[ 0 ] = -1; + buildBrush->portalareas[ 1 ] = -1; + buildBrush->entityNum = numMapEntities - 1; + buildBrush->brushNum = entitySourceBrushes; + + /* if there are mirrored planes, the entire brush is invalid */ + if ( !RemoveDuplicateBrushPlanes( buildBrush ) ) { + return; + } + + /* get the content for the entire brush */ + SetBrushContents( buildBrush ); + + /* allow detail brushes to be removed */ + if ( nodetail && ( buildBrush->compileFlags & C_DETAIL ) ) { + //% FreeBrush( buildBrush ); + return; + } + + /* allow liquid brushes to be removed */ + if ( nowater && ( buildBrush->compileFlags & C_LIQUID ) ) { + //% FreeBrush( buildBrush ); + return; + } + + /* ydnar: allow hint brushes to be removed */ + if ( noHint && ( buildBrush->compileFlags & C_HINT ) ) { + //% FreeBrush( buildBrush ); + return; + } + + /* finish the brush */ + FinishBrush( noCollapseGroups ); +} + + +/*Spike: we only notice that its a func_detail AFTER we have parsed the entity. So go back and flag the brushes as detail instead.*/ +static void ForceBrushesToDetail(entity_t *ent, qboolean illusionary) +{ + brush_t *b; + for ( b = ent->brushes; b != NULL; b = b->next ) + { + if (illusionary) + b->contentFlags &= C_SOLID; + if (!b->detail && !(b->compileFlags & (C_DETAIL|C_STRUCTURAL))) + { + c_structural--; + c_detail++; + b->compileFlags |= C_DETAIL; + b->detail = qtrue; + } + } +} + +/* + MoveBrushesToWorld() + takes all of the brushes from the current entity and + adds them to the world's brush list + (used by func_group) + */ + +void AdjustBrushesForOrigin( entity_t *ent ); +void MoveBrushesToWorld( entity_t *ent ){ + brush_t *b, *next; + parseMesh_t *pm; + + /* we need to undo the common/origin adjustment, and instead shift them by the entity key origin */ + VectorScale( ent->origin, -1, ent->originbrush_origin ); + AdjustBrushesForOrigin( ent ); + VectorClear( ent->originbrush_origin ); + + /* move brushes */ + for ( b = ent->brushes; b != NULL; b = next ) + { + /* get next brush */ + next = b->next; + + /* link opaque brushes to head of list, translucent brushes to end */ + if ( b->opaque || entities[ 0 ].lastBrush == NULL ) { + b->next = entities[ 0 ].brushes; + entities[ 0 ].brushes = b; + if ( entities[ 0 ].lastBrush == NULL ) { + entities[ 0 ].lastBrush = b; + } + } + else + { + b->next = NULL; + entities[ 0 ].lastBrush->next = b; + entities[ 0 ].lastBrush = b; + } + } + ent->brushes = NULL; + + /* ydnar: move colormod brushes */ + if ( ent->colorModBrushes != NULL ) { + for ( b = ent->colorModBrushes; b->nextColorModBrush != NULL; b = b->nextColorModBrush ) ; + + b->nextColorModBrush = entities[ 0 ].colorModBrushes; + entities[ 0 ].colorModBrushes = ent->colorModBrushes; + + ent->colorModBrushes = NULL; + } + + /* move patches */ + if ( ent->patches != NULL ) { + for ( pm = ent->patches; pm->next != NULL; pm = pm->next ) ; + + pm->next = entities[ 0 ].patches; + entities[ 0 ].patches = ent->patches; + + ent->patches = NULL; + } +} + + + +/* + AdjustBrushesForOrigin() + */ + +void AdjustBrushesForOrigin( entity_t *ent ){ + + int i; + side_t *s; + vec_t newdist; + brush_t *b; + parseMesh_t *p; + + /* walk brush list */ + for ( b = ent->brushes; b != NULL; b = b->next ) + { + /* offset brush planes */ + for ( i = 0; i < b->numsides; i++ ) + { + /* get brush side */ + s = &b->sides[ i ]; + + /* offset side plane */ + newdist = mapplanes[ s->planenum ].dist - DotProduct( mapplanes[ s->planenum ].normal, ent->originbrush_origin ); + + /* find a new plane */ + s->planenum = FindFloatPlane( mapplanes[ s->planenum ].normal, newdist, 0, NULL ); + } + + /* rebuild brush windings (ydnar: just offsetting the winding above should be fine) */ + CreateBrushWindings( b ); + } + + /* walk patch list */ + for ( p = ent->patches; p != NULL; p = p->next ) + { + for ( i = 0; i < ( p->mesh.width * p->mesh.height ); i++ ) + VectorSubtract( p->mesh.verts[ i ].xyz, ent->originbrush_origin, p->mesh.verts[ i ].xyz ); + } +} + + + +/* + SetEntityBounds() - ydnar + finds the bounds of an entity's brushes (necessary for terrain-style generic metashaders) + */ + +void SetEntityBounds( entity_t *e ){ + int i; + brush_t *b; + parseMesh_t *p; + vec3_t mins, maxs; + const char *value; + + + + + /* walk the entity's brushes/patches and determine bounds */ + ClearBounds( mins, maxs ); + for ( b = e->brushes; b; b = b->next ) + { + AddPointToBounds( b->mins, mins, maxs ); + AddPointToBounds( b->maxs, mins, maxs ); + } + for ( p = e->patches; p; p = p->next ) + { + for ( i = 0; i < ( p->mesh.width * p->mesh.height ); i++ ) + AddPointToBounds( p->mesh.verts[ i ].xyz, mins, maxs ); + } + + /* try to find explicit min/max key */ + value = ValueForKey( e, "min" ); + if ( value[ 0 ] != '\0' ) { + GetVectorForKey( e, "min", mins ); + } + value = ValueForKey( e, "max" ); + if ( value[ 0 ] != '\0' ) { + GetVectorForKey( e, "max", maxs ); + } + + /* store the bounds */ + for ( b = e->brushes; b; b = b->next ) + { + VectorCopy( mins, b->eMins ); + VectorCopy( maxs, b->eMaxs ); + } + for ( p = e->patches; p; p = p->next ) + { + VectorCopy( mins, p->eMins ); + VectorCopy( maxs, p->eMaxs ); + } +} + + + +/* + LoadEntityIndexMap() - ydnar + based on LoadAlphaMap() from terrain.c, a little more generic + */ + +void LoadEntityIndexMap( entity_t *e ){ + int i, size, numLayers, w, h; + const char *value, *indexMapFilename, *shader; + char ext[ MAX_QPATH ], offset[ 4096 ], *search, *space; + byte *pixels; + unsigned int *pixels32; + indexMap_t *im; + brush_t *b; + parseMesh_t *p; + + + /* this only works with bmodel ents */ + if ( e->brushes == NULL && e->patches == NULL ) { + return; + } + + /* determine if there is an index map (support legacy "alphamap" key as well) */ + value = ValueForKey( e, "_indexmap" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( e, "alphamap" ); + } + if ( value[ 0 ] == '\0' ) { + return; + } + indexMapFilename = value; + + /* get number of layers (support legacy "layers" key as well) */ + value = ValueForKey( e, "_layers" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( e, "layers" ); + } + if ( value[ 0 ] == '\0' ) { + Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" has missing \"_layers\" or \"layers\" key\n", indexMapFilename ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + return; + } + numLayers = atoi( value ); + if ( numLayers < 1 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" has < 1 layer (%d)\n", indexMapFilename, numLayers ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + return; + } + + /* get base shader name (support legacy "shader" key as well) */ + value = ValueForKey( mapEnt, "_shader" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( e, "shader" ); + } + if ( value[ 0 ] == '\0' ) { + Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" has missing \"_shader\" or \"shader\" key\n", indexMapFilename ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + return; + } + shader = value; + + /* note it */ + Sys_FPrintf( SYS_VRB, "Entity %d (%s) has shader index map \"%s\"\n", mapEnt->mapEntityNum, ValueForKey( e, "classname" ), indexMapFilename ); + + /* get index map file extension */ + ExtractFileExtension( indexMapFilename, ext ); + + /* handle tga image */ + if ( !Q_stricmp( ext, "tga" ) ) { + /* load it */ + Load32BitImage( indexMapFilename, &pixels32, &w, &h ); + + /* convert to bytes */ + size = w * h; + pixels = safe_malloc( size ); + for ( i = 0; i < size; i++ ) + { + pixels[ i ] = ( ( pixels32[ i ] & 0xFF ) * numLayers ) / 256; + if ( pixels[ i ] >= numLayers ) { + pixels[ i ] = numLayers - 1; + } + } + + /* free the 32 bit image */ + free( pixels32 ); + } + else + { + /* load it */ + Load256Image( indexMapFilename, &pixels, NULL, &w, &h ); + + /* debug code */ + //% Sys_Printf( "-------------------------------" ); + + /* fix up out-of-range values */ + size = w * h; + for ( i = 0; i < size; i++ ) + { + if ( pixels[ i ] >= numLayers ) { + pixels[ i ] = numLayers - 1; + } + + /* debug code */ + //% if( (i % w) == 0 ) + //% Sys_Printf( "\n" ); + //% Sys_Printf( "%c", pixels[ i ] + '0' ); + } + + /* debug code */ + //% Sys_Printf( "\n-------------------------------\n" ); + } + + /* the index map must be at least 2x2 pixels */ + if ( w < 2 || h < 2 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Entity with index/alpha map \"%s\" is smaller than 2x2 pixels\n", indexMapFilename ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + free( pixels ); + return; + } + + /* create a new index map */ + im = safe_malloc( sizeof( *im ) ); + memset( im, 0, sizeof( *im ) ); + + /* set it up */ + im->w = w; + im->h = h; + im->numLayers = numLayers; + strcpy( im->name, indexMapFilename ); + strcpy( im->shader, shader ); + im->pixels = pixels; + + /* get height offsets */ + value = ValueForKey( mapEnt, "_offsets" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( e, "offsets" ); + } + if ( value[ 0 ] != '\0' ) { + /* value is a space-seperated set of numbers */ + strcpy( offset, value ); + search = offset; + + /* get each value */ + for ( i = 0; i < 256 && *search != '\0'; i++ ) + { + space = strstr( search, " " ); + if ( space != NULL ) { + *space = '\0'; + } + im->offsets[ i ] = atof( search ); + if ( space == NULL ) { + break; + } + search = space + 1; + } + } + + /* store the index map in every brush/patch in the entity */ + for ( b = e->brushes; b != NULL; b = b->next ) + b->im = im; + for ( p = e->patches; p != NULL; p = p->next ) + p->im = im; +} + + + + + + + +/* + ParseMapEntity() + parses a single entity out of a map file + */ + +static qboolean ParseMapEntity( qboolean onlyLights, qboolean noCollapseGroups ){ + epair_t *ep; + const char *classname, *value; + float lightmapScale, shadeAngle; + int lightmapSampleSize; + char shader[ MAX_QPATH ]; + shaderInfo_t *celShader = NULL; + brush_t *brush; + parseMesh_t *patch; + enum + { + funcgroup_not, //regular entity. + funcgroup_group, //just part of world + funcgroup_detail, //solid detail + funcgroup_detail_illusionary //non-solid detail + } funcGroupType; + int castShadows, recvShadows; + + + /* eof check */ + if ( !GetToken( qtrue ) ) { + return qfalse; + } + + /* conformance check */ + if ( strcmp( token, "{" ) ) { + Sys_FPrintf( SYS_WRN, "WARNING: ParseEntity: { not found, found %s on line %d - last entity was at: <%4.2f, %4.2f, %4.2f>...\n" + "Continuing to process map, but resulting BSP may be invalid.\n", + token, scriptline, entities[ numEntities ].origin[ 0 ], entities[ numEntities ].origin[ 1 ], entities[ numEntities ].origin[ 2 ] ); + return qfalse; + } + + /* range check */ + AUTOEXPAND_BY_REALLOC( entities, numEntities, allocatedEntities, 32 ); + + /* setup */ + entitySourceBrushes = 0; + mapEnt = &entities[ numEntities ]; + numEntities++; + memset( mapEnt, 0, sizeof( *mapEnt ) ); + + /* ydnar: true entity numbering */ + mapEnt->mapEntityNum = numMapEntities; + numMapEntities++; + + /* loop */ + while ( 1 ) + { + /* get initial token */ + if ( !GetToken( qtrue ) ) { + Sys_FPrintf( SYS_WRN, "WARNING: ParseEntity: EOF without closing brace\n" + "Continuing to process map, but resulting BSP may be invalid.\n" ); + return qfalse; + } + + if ( !strcmp( token, "}" ) ) { + break; + } + + if ( !strcmp( token, "{" ) ) { + /* parse a brush or patch */ + if ( !GetToken( qtrue ) ) { + break; + } + + /* check */ + if ( !strcmp( token, "patchDef2" ) || !strcmp( token, "patchDef2WS" ) ) { + numMapPatches++; + ParsePatch( onlyLights, qfalse ); + } else if ( !strcmp( token, "patchDef3" ) || !strcmp( token, "patchDef3WS" ) ) { + numMapPatches++; + ParsePatch( onlyLights, qtrue ); +// } else if ( !strcmp( token, "patchDefWS" ) ) { +// numMapPatches++; +// ParsePatch( onlyLights, qfalse ); + } else if ( !strcmp( token, "terrainDef" ) ) { + //% ParseTerrain(); + Sys_FPrintf( SYS_WRN, "WARNING: Terrain entity parsing not supported in this build.\n" ); /* ydnar */ + } else if ( !strcmp( token, "brushDef" ) ) { + if ( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) { + Error( "Old brush format not allowed in new brush format map" ); + } + g_bBrushPrimit = BPRIMIT_NEWBRUSHES; + + /* parse brush primitive */ + ParseBrush( onlyLights, noCollapseGroups ); + } else { + if ( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) { + Error( "New brush format not allowed in old brush format map" ); + } + g_bBrushPrimit = BPRIMIT_OLDBRUSHES; + + /* parse old brush format */ + UnGetToken(); + ParseBrush( onlyLights, noCollapseGroups ); + } + entitySourceBrushes++; + } + else + { + /* parse a key / value pair */ + ep = ParseEPair(); + + /* ydnar: 2002-07-06 fixed wolf bug with empty epairs */ + if ( ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' ) { + ep->next = mapEnt->epairs; + mapEnt->epairs = ep; + } + } + } + + /* ydnar: get classname */ + classname = ValueForKey( mapEnt, "classname" ); + + /* ydnar: only lights? */ + if ( onlyLights && Q_strncasecmp( classname, "light", 5 ) ) { + numEntities--; + return qtrue; + } + + /* ydnar: determine if this is a func_group */ + if ( !Q_stricmp( "func_group", classname ) ) { + funcGroupType = funcgroup_group; + } + /* spike: q1 .maps have no way to set any detailbrush flags, so some workarounds exist... */ + else if ( !Q_stricmp( "func_detail", classname ) || !Q_stricmp( "func_detail_wall", classname ) || !Q_stricmp( "func_detail_fence", classname ) ) { + funcGroupType = funcgroup_detail; + } + else if ( !Q_stricmp( "func_detail_illusionary ", classname ) ) { + funcGroupType = funcgroup_detail_illusionary; + }else{ + funcGroupType = funcgroup_not; + } + + if (!Q_stricmp("misc_prefab", classname)) { + numEntities--; +// AddScriptToStack(ValueForKey(mapEnt, "model"), 0); + return qtrue; + } + + /* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */ + if ( funcGroupType!=funcgroup_not || mapEnt->mapEntityNum == 0 ) { + //% Sys_Printf( "World: %d\n", mapEnt->mapEntityNum ); + castShadows = WORLDSPAWN_CAST_SHADOWS; + recvShadows = WORLDSPAWN_RECV_SHADOWS; + } + + /* other entities don't cast any shadows, but recv worldspawn shadows */ + else + { + //% Sys_Printf( "Entity: %d\n", mapEnt->mapEntityNum ); + castShadows = ENTITY_CAST_SHADOWS; + recvShadows = ENTITY_RECV_SHADOWS; + } + + /* get explicit shadow flags */ + GetEntityShadowFlags( mapEnt, NULL, &castShadows, &recvShadows ); + + /* vortex: added _ls key (short name of lightmapscale) */ + /* ydnar: get lightmap scaling value for this entity */ + lightmapScale = 0.0f; + if ( strcmp( "", ValueForKey( mapEnt, "lightmapscale" ) ) || + strcmp( "", ValueForKey( mapEnt, "_lightmapscale" ) ) || + strcmp( "", ValueForKey( mapEnt, "_ls" ) ) ) { + /* get lightmap scale from entity */ + lightmapScale = FloatForKey( mapEnt, "lightmapscale" ); + if ( lightmapScale <= 0.0f ) { + lightmapScale = FloatForKey( mapEnt, "_lightmapscale" ); + } + if ( lightmapScale <= 0.0f ) { + lightmapScale = FloatForKey( mapEnt, "_ls" ); + } + if ( lightmapScale < 0.0f ) { + lightmapScale = 0.0f; + } + if ( lightmapScale > 0.0f ) { + Sys_Printf( "Entity %d (%s) has lightmap scale of %.4f\n", mapEnt->mapEntityNum, classname, lightmapScale ); + } + } + + /* ydnar: get cel shader :) for this entity */ + value = ValueForKey( mapEnt, "_celshader" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ 0 ], "_celshader" ); + } + if ( value[ 0 ] != '\0' ) { + if ( strcmp( value, "none" ) ) { + sprintf( shader, "textures/%s", value ); + celShader = ShaderInfoForShader( shader ); + Sys_Printf( "Entity %d (%s) has cel shader %s\n", mapEnt->mapEntityNum, classname, celShader->shader ); + } + else + { + celShader = NULL; + } + } + else{ + celShader = ( *globalCelShader ? ShaderInfoForShader( globalCelShader ) : NULL ); + } + + /* jal : entity based _shadeangle */ + shadeAngle = 0.0f; + if ( strcmp( "", ValueForKey( mapEnt, "_shadeangle" ) ) ) { + shadeAngle = FloatForKey( mapEnt, "_shadeangle" ); + } + /* vortex' aliases */ + else if ( strcmp( "", ValueForKey( mapEnt, "_smoothnormals" ) ) ) { + shadeAngle = FloatForKey( mapEnt, "_smoothnormals" ); + } + else if ( strcmp( "", ValueForKey( mapEnt, "_sn" ) ) ) { + shadeAngle = FloatForKey( mapEnt, "_sn" ); + } + else if ( strcmp( "", ValueForKey( mapEnt, "_smooth" ) ) ) { + shadeAngle = FloatForKey( mapEnt, "_smooth" ); + } + + if ( shadeAngle < 0.0f ) { + shadeAngle = 0.0f; + } + + if ( shadeAngle > 0.0f ) { + Sys_Printf( "Entity %d (%s) has shading angle of %.4f\n", mapEnt->mapEntityNum, classname, shadeAngle ); + } + + /* jal : entity based _samplesize */ + lightmapSampleSize = 0; + if ( strcmp( "", ValueForKey( mapEnt, "_lightmapsamplesize" ) ) ) { + lightmapSampleSize = IntForKey( mapEnt, "_lightmapsamplesize" ); + } + else if ( strcmp( "", ValueForKey( mapEnt, "_samplesize" ) ) ) { + lightmapSampleSize = IntForKey( mapEnt, "_samplesize" ); + } + + if ( lightmapSampleSize < 0 ) { + lightmapSampleSize = 0; + } + + if ( lightmapSampleSize > 0 ) { + Sys_Printf( "Entity %d (%s) has lightmap sample size of %d\n", mapEnt->mapEntityNum, classname, lightmapSampleSize ); + } + + /* attach stuff to everything in the entity */ + for ( brush = mapEnt->brushes; brush != NULL; brush = brush->next ) + { + brush->entityNum = mapEnt->mapEntityNum; + brush->castShadows = castShadows; + brush->recvShadows = recvShadows; + brush->lightmapSampleSize = lightmapSampleSize; + brush->lightmapScale = lightmapScale; + brush->celShader = celShader; + brush->shadeAngleDegrees = shadeAngle; + } + + for ( patch = mapEnt->patches; patch != NULL; patch = patch->next ) + { + patch->entityNum = mapEnt->mapEntityNum; + patch->castShadows = castShadows; + patch->recvShadows = recvShadows; + patch->lightmapSampleSize = lightmapSampleSize; + patch->lightmapScale = lightmapScale; + patch->celShader = celShader; + } + + /* ydnar: gs mods: set entity bounds */ + SetEntityBounds( mapEnt ); + + /* ydnar: gs mods: load shader index map (equivalent to old terrain alphamap) */ + LoadEntityIndexMap( mapEnt ); + + /* get entity origin and adjust brushes */ + GetVectorForKey( mapEnt, "origin", mapEnt->origin ); + if ( mapEnt->originbrush_origin[ 0 ] || mapEnt->originbrush_origin[ 1 ] || mapEnt->originbrush_origin[ 2 ] ) { + AdjustBrushesForOrigin( mapEnt ); + } + + /* group_info entities are just for editor grouping (fixme: leak!) */ + if ( !noCollapseGroups && !Q_stricmp( "group_info", classname ) ) { + numEntities--; + return qtrue; + } + + if (funcGroupType == funcgroup_detail_illusionary) { + ForceBrushesToDetail( mapEnt, qtrue ); + } + else if (funcGroupType == funcgroup_detail) { + ForceBrushesToDetail( mapEnt, qfalse ); + } + + /* group entities are just for editor convenience, toss all brushes into worldspawn */ + if ( !noCollapseGroups && funcGroupType != funcgroup_not ) { + MoveBrushesToWorld( mapEnt ); + numEntities--; + return qtrue; + } + + /* done */ + return qtrue; +} + + + +/* + LoadMapFile() + loads a map file into a list of entities + */ + +void LoadMapFile( char *filename, qboolean onlyLights, qboolean noCollapseGroups ){ + FILE *file; + brush_t *b; + int oldNumEntities = 0, numMapBrushes; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" ); + Sys_Printf( "Loading %s\n", filename ); + + /* hack */ + file = SafeOpenRead( filename ); + fclose( file ); + + /* load the map file */ + LoadScriptFile( filename, -1 ); + + /* setup */ + if ( onlyLights ) { + oldNumEntities = numEntities; + } + else{ + numEntities = 0; + } + + /* initial setup */ + numMapDrawSurfs = 0; + c_detail = 0; + g_bBrushPrimit = BPRIMIT_UNDEFINED; + + /* allocate a very large temporary brush for building the brushes as they are loaded */ + buildBrush = AllocBrush( MAX_BUILD_SIDES ); + + /* parse the map file */ + while ( ParseMapEntity( onlyLights, noCollapseGroups ) ) ; + + /* light loading */ + if ( onlyLights ) { + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d light entities\n", numEntities - oldNumEntities ); + } + else + { + /* set map bounds */ + ClearBounds( mapMins, mapMaxs ); + for ( b = entities[ 0 ].brushes; b; b = b->next ) + { + AddPointToBounds( b->mins, mapMins, mapMaxs ); + AddPointToBounds( b->maxs, mapMins, mapMaxs ); + } + + /* get brush counts */ + numMapBrushes = CountBrushList( entities[ 0 ].brushes ); + if ( (float) c_detail / (float) numMapBrushes < 0.10f && numMapBrushes > 500 ) { + Sys_FPrintf( SYS_WRN, "WARNING: Over 90 percent structural map detected. Compile time may be adversely affected.\n" ); + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d total world brushes\n", numMapBrushes ); + Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_detail ); + Sys_FPrintf( SYS_VRB, "%9d patches\n", numMapPatches ); + Sys_FPrintf( SYS_VRB, "%9d boxbevels\n", c_boxbevels ); + Sys_FPrintf( SYS_VRB, "%9d edgebevels\n", c_edgebevels ); + Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities ); + Sys_FPrintf( SYS_VRB, "%9d planes\n", nummapplanes ); + Sys_Printf( "%9d areaportals\n", c_areaportals ); + Sys_Printf( "Size: %5.0f, %5.0f, %5.0f to %5.0f, %5.0f, %5.0f\n", + mapMins[ 0 ], mapMins[ 1 ], mapMins[ 2 ], + mapMaxs[ 0 ], mapMaxs[ 1 ], mapMaxs[ 2 ] ); + + /* write bogus map */ + if ( fakemap ) { + WriteBSPBrushMap( "fakemap.map", entities[ 0 ].brushes ); + } + } +} diff --git a/tools/vmap/mesh.c b/tools/vmap/mesh.c new file mode 100644 index 0000000..ec11192 --- /dev/null +++ b/tools/vmap/mesh.c @@ -0,0 +1,860 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define MESH_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + LerpDrawVert() + returns an 50/50 interpolated vert + */ + +void LerpDrawVert( bspDrawVert_t *a, bspDrawVert_t *b, bspDrawVert_t *out ){ + int k; + + + out->xyz[ 0 ] = 0.5 * ( a->xyz[ 0 ] + b->xyz[ 0 ] ); + out->xyz[ 1 ] = 0.5 * ( a->xyz[ 1 ] + b->xyz[ 1 ] ); + out->xyz[ 2 ] = 0.5 * ( a->xyz[ 2 ] + b->xyz[ 2 ] ); + + out->st[ 0 ] = 0.5 * ( a->st[ 0 ] + b->st[ 0 ] ); + out->st[ 1 ] = 0.5 * ( a->st[ 1 ] + b->st[ 1 ] ); + + for ( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + out->lightmap[ k ][ 0 ] = 0.5f * ( a->lightmap[ k ][ 0 ] + b->lightmap[ k ][ 0 ] ); + out->lightmap[ k ][ 1 ] = 0.5f * ( a->lightmap[ k ][ 1 ] + b->lightmap[ k ][ 1 ] ); + out->color[ k ][ 0 ] = ( a->color[ k ][ 0 ] + b->color[ k ][ 0 ] ) >> 1; + out->color[ k ][ 1 ] = ( a->color[ k ][ 1 ] + b->color[ k ][ 1 ] ) >> 1; + out->color[ k ][ 2 ] = ( a->color[ k ][ 2 ] + b->color[ k ][ 2 ] ) >> 1; + out->color[ k ][ 3 ] = ( a->color[ k ][ 3 ] + b->color[ k ][ 3 ] ) >> 1; + } + + /* ydnar: added normal interpolation */ + out->normal[ 0 ] = 0.5f * ( a->normal[ 0 ] + b->normal[ 0 ] ); + out->normal[ 1 ] = 0.5f * ( a->normal[ 1 ] + b->normal[ 1 ] ); + out->normal[ 2 ] = 0.5f * ( a->normal[ 2 ] + b->normal[ 2 ] ); + + /* if the interpolant created a bogus normal, just copy the normal from a */ + if ( VectorNormalize( out->normal, out->normal ) == 0 ) { + VectorCopy( a->normal, out->normal ); + } +} + + + +/* + LerpDrawVertAmount() + returns a biased interpolated vert + */ + +void LerpDrawVertAmount( bspDrawVert_t *a, bspDrawVert_t *b, float amount, bspDrawVert_t *out ){ + int k; + + + out->xyz[ 0 ] = a->xyz[ 0 ] + amount * ( b->xyz[ 0 ] - a->xyz[ 0 ] ); + out->xyz[ 1 ] = a->xyz[ 1 ] + amount * ( b->xyz[ 1 ] - a->xyz[ 1 ] ); + out->xyz[ 2 ] = a->xyz[ 2 ] + amount * ( b->xyz[ 2 ] - a->xyz[ 2 ] ); + + out->st[ 0 ] = a->st[ 0 ] + amount * ( b->st[ 0 ] - a->st[ 0 ] ); + out->st[ 1 ] = a->st[ 1 ] + amount * ( b->st[ 1 ] - a->st[ 1 ] ); + + for ( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + out->lightmap[ k ][ 0 ] = a->lightmap[ k ][ 0 ] + amount * ( b->lightmap[ k ][ 0 ] - a->lightmap[ k ][ 0 ] ); + out->lightmap[ k ][ 1 ] = a->lightmap[ k ][ 1 ] + amount * ( b->lightmap[ k ][ 1 ] - a->lightmap[ k ][ 1 ] ); + out->color[ k ][ 0 ] = a->color[ k ][ 0 ] + amount * ( b->color[ k ][ 0 ] - a->color[ k ][ 0 ] ); + out->color[ k ][ 1 ] = a->color[ k ][ 1 ] + amount * ( b->color[ k ][ 1 ] - a->color[ k ][ 1 ] ); + out->color[ k ][ 2 ] = a->color[ k ][ 2 ] + amount * ( b->color[ k ][ 2 ] - a->color[ k ][ 2 ] ); + out->color[ k ][ 3 ] = a->color[ k ][ 3 ] + amount * ( b->color[ k ][ 3 ] - a->color[ k ][ 3 ] ); + } + + out->normal[ 0 ] = a->normal[ 0 ] + amount * ( b->normal[ 0 ] - a->normal[ 0 ] ); + out->normal[ 1 ] = a->normal[ 1 ] + amount * ( b->normal[ 1 ] - a->normal[ 1 ] ); + out->normal[ 2 ] = a->normal[ 2 ] + amount * ( b->normal[ 2 ] - a->normal[ 2 ] ); + + /* if the interpolant created a bogus normal, just copy the normal from a */ + if ( VectorNormalize( out->normal, out->normal ) == 0 ) { + VectorCopy( a->normal, out->normal ); + } +} + + +void FreeMesh( mesh_t *m ) { + free( m->verts ); + free( m ); +} + +void PrintMesh( mesh_t *m ) { + int i, j; + + for ( i = 0 ; i < m->height ; i++ ) { + for ( j = 0 ; j < m->width ; j++ ) { + Sys_Printf( "(%5.2f %5.2f %5.2f) " + , m->verts[i * m->width + j].xyz[0] + , m->verts[i * m->width + j].xyz[1] + , m->verts[i * m->width + j].xyz[2] ); + } + Sys_Printf( "\n" ); + } +} + + +mesh_t *CopyMesh( mesh_t *mesh ) { + mesh_t *out; + int size; + + out = safe_malloc( sizeof( *out ) ); + out->width = mesh->width; + out->height = mesh->height; + out->subdiv_x = mesh->subdiv_x; + out->subdiv_y = mesh->subdiv_y; + + size = out->width * out->height * sizeof( *out->verts ); + out->verts = safe_malloc( size ); + memcpy( out->verts, mesh->verts, size ); + + return out; +} + + +/* + TransposeMesh() + returns a transposed copy of the mesh, freeing the original + */ + +mesh_t *TransposeMesh( mesh_t *in ) { + int w, h; + mesh_t *out; + + out = safe_malloc( sizeof( *out ) ); + out->width = in->height; + out->height = in->width; + out->verts = safe_malloc( out->width * out->height * sizeof( bspDrawVert_t ) ); + + for ( h = 0 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width ; w++ ) { + out->verts[ w * in->height + h ] = in->verts[ h * in->width + w ]; + } + } + + FreeMesh( in ); + + return out; +} + +void InvertMesh( mesh_t *in ) { + int w, h; + bspDrawVert_t temp; + + for ( h = 0 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width / 2 ; w++ ) { + temp = in->verts[ h * in->width + w ]; + in->verts[ h * in->width + w ] = in->verts[ h * in->width + in->width - 1 - w ]; + in->verts[ h * in->width + in->width - 1 - w ] = temp; + } + } +} + +/* + ================= + MakeMeshNormals + + ================= + */ +void MakeMeshNormals( mesh_t in ){ + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count; + vec3_t base; + vec3_t delta; + int x, y; + bspDrawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; + int neighbors[8][2] = + { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + + wrapWidth = qfalse; + for ( i = 0 ; i < in.height ; i++ ) { + VectorSubtract( in.verts[i * in.width].xyz, + in.verts[i * in.width + in.width - 1].xyz, delta ); + len = VectorLength( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == in.height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < in.width ; i++ ) { + VectorSubtract( in.verts[i].xyz, + in.verts[i + ( in.height - 1 ) * in.width].xyz, delta ); + len = VectorLength( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == in.width ) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < in.width ; i++ ) { + for ( j = 0 ; j < in.height ; j++ ) { + count = 0; + dv = &in.verts[j * in.width + i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = in.width - 1 + x; + } + else if ( x >= in.width ) { + x = 1 + x - in.width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = in.height - 1 + y; + } + else if ( y >= in.height ) { + y = 1 + y - in.height; + } + } + + if ( x < 0 || x >= in.width || y < 0 || y >= in.height ) { + break; // edge of patch + } + VectorSubtract( in.verts[y * in.width + x].xyz, base, temp ); + if ( VectorNormalize( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } + else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[( k + 1 ) & 7] ) { + continue; // didn't get two points + } + CrossProduct( around[( k + 1 ) & 7], around[k], normal ); + if ( VectorNormalize( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//Sys_Printf("bad normal\n"); + count = 1; + } + VectorNormalize( sum, dv->normal ); + } + } +} + +/* + PutMeshOnCurve() + drops the aproximating points onto the curve + ydnar: fixme: make this use LerpDrawVert() rather than this complicated mess + */ + +void PutMeshOnCurve( mesh_t in ) { + int i, j, l, m; + float prev, next; + + + // put all the aproximating points on the curve + for ( i = 0 ; i < in.width ; i++ ) { + for ( j = 1 ; j < in.height ; j += 2 ) { + for ( l = 0 ; l < 3 ; l++ ) { + prev = ( in.verts[j * in.width + i].xyz[l] + in.verts[( j + 1 ) * in.width + i].xyz[l] ) * 0.5; + next = ( in.verts[j * in.width + i].xyz[l] + in.verts[( j - 1 ) * in.width + i].xyz[l] ) * 0.5; + in.verts[j * in.width + i].xyz[l] = ( prev + next ) * 0.5; + + /* ydnar: interpolating st coords */ + if ( l < 2 ) { + prev = ( in.verts[j * in.width + i].st[l] + in.verts[( j + 1 ) * in.width + i].st[l] ) * 0.5; + next = ( in.verts[j * in.width + i].st[l] + in.verts[( j - 1 ) * in.width + i].st[l] ) * 0.5; + in.verts[j * in.width + i].st[l] = ( prev + next ) * 0.5; + + for ( m = 0; m < MAX_LIGHTMAPS; m++ ) + { + prev = ( in.verts[j * in.width + i].lightmap[ m ][l] + in.verts[( j + 1 ) * in.width + i].lightmap[ m ][l] ) * 0.5; + next = ( in.verts[j * in.width + i].lightmap[ m ][l] + in.verts[( j - 1 ) * in.width + i].lightmap[ m ][l] ) * 0.5; + in.verts[j * in.width + i].lightmap[ m ][l] = ( prev + next ) * 0.5; + } + } + } + } + } + + for ( j = 0 ; j < in.height ; j++ ) { + for ( i = 1 ; i < in.width ; i += 2 ) { + for ( l = 0 ; l < 3 ; l++ ) { + prev = ( in.verts[j * in.width + i].xyz[l] + in.verts[j * in.width + i + 1].xyz[l] ) * 0.5; + next = ( in.verts[j * in.width + i].xyz[l] + in.verts[j * in.width + i - 1].xyz[l] ) * 0.5; + in.verts[j * in.width + i].xyz[l] = ( prev + next ) * 0.5; + + /* ydnar: interpolating st coords */ + if ( l < 2 ) { + prev = ( in.verts[j * in.width + i].st[l] + in.verts[j * in.width + i + 1].st[l] ) * 0.5; + next = ( in.verts[j * in.width + i].st[l] + in.verts[j * in.width + i - 1].st[l] ) * 0.5; + in.verts[j * in.width + i].st[l] = ( prev + next ) * 0.5; + + for ( m = 0; m < MAX_LIGHTMAPS; m++ ) + { + prev = ( in.verts[j * in.width + i].lightmap[ m ][l] + in.verts[j * in.width + i + 1].lightmap[ m ][l] ) * 0.5; + next = ( in.verts[j * in.width + i].lightmap[ m ][l] + in.verts[j * in.width + i - 1].lightmap[ m ][l] ) * 0.5; + in.verts[j * in.width + i].lightmap[ m ][l] = ( prev + next ) * 0.5; + } + } + } + } + } +} + + +/* + ================= + SubdivideMesh + + ================= + */ + +mesh_t *SubdivideMesh( mesh_t in, float maxError, float minLength ){ + int i, j, k, l; + bspDrawVert_t prev, next, mid; + vec3_t prevxyz, nextxyz, midxyz; + vec3_t delta; + float len; + mesh_t out; + + /* ydnar: static for os x */ + MAC_STATIC bspDrawVert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + + + out.width = in.width; + out.height = in.height; + out.subdiv_x = in.subdiv_x; + out.subdiv_y = in.subdiv_y; + if (in.subdiv_x == 0 && in.subdiv_y == 0) + { //exact CPs + out.verts = in.verts; + return CopyMesh( &out ); + } + + for ( i = 0 ; i < in.width ; i++ ) { + for ( j = 0 ; j < in.height ; j++ ) { + expand[j][i] = in.verts[j * in.width + i]; + } + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < out.width ; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0 ; i < out.height ; i++ ) { + for ( l = 0 ; l < 3 ; l++ ) { + prevxyz[l] = expand[i][j + 1].xyz[l] - expand[i][j].xyz[l]; + nextxyz[l] = expand[i][j + 2].xyz[l] - expand[i][j + 1].xyz[l]; + midxyz[l] = ( expand[i][j].xyz[l] + expand[i][j + 1].xyz[l] * 2 + + expand[i][j + 2].xyz[l] ) * 0.25; + } + + // if the span length is too long, force a subdivision + if ( VectorLength( prevxyz ) > minLength + || VectorLength( nextxyz ) > minLength ) { + break; + } + + // see if this midpoint is off far enough to subdivide + VectorSubtract( expand[i][j + 1].xyz, midxyz, delta ); + len = VectorLength( delta ); + if ( len > maxError ) { + break; + } + } + + if ( out.width + 2 >= MAX_EXPANDED_AXIS ) { + break; // can't subdivide any more + } + + if ( i == out.height ) { + continue; // didn't need subdivision + } + + // insert two columns and replace the peak + out.width += 2; + + for ( i = 0 ; i < out.height ; i++ ) { + LerpDrawVert( &expand[i][j], &expand[i][j + 1], &prev ); + LerpDrawVert( &expand[i][j + 1], &expand[i][j + 2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = out.width - 1 ; k > j + 3 ; k-- ) { + expand[i][k] = expand[i][k - 2]; + } + expand[i][j + 1] = prev; + expand[i][j + 2] = mid; + expand[i][j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + // vertical subdivisions + for ( j = 0 ; j + 2 < out.height ; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0 ; i < out.width ; i++ ) { + for ( l = 0 ; l < 3 ; l++ ) { + prevxyz[l] = expand[j + 1][i].xyz[l] - expand[j][i].xyz[l]; + nextxyz[l] = expand[j + 2][i].xyz[l] - expand[j + 1][i].xyz[l]; + midxyz[l] = ( expand[j][i].xyz[l] + expand[j + 1][i].xyz[l] * 2 + + expand[j + 2][i].xyz[l] ) * 0.25; + } + + // if the span length is too long, force a subdivision + if ( VectorLength( prevxyz ) > minLength + || VectorLength( nextxyz ) > minLength ) { + break; + } + // see if this midpoint is off far enough to subdivide + VectorSubtract( expand[j + 1][i].xyz, midxyz, delta ); + len = VectorLength( delta ); + if ( len > maxError ) { + break; + } + } + + if ( out.height + 2 >= MAX_EXPANDED_AXIS ) { + break; // can't subdivide any more + } + + if ( i == out.width ) { + continue; // didn't need subdivision + } + + // insert two columns and replace the peak + out.height += 2; + + for ( i = 0 ; i < out.width ; i++ ) { + LerpDrawVert( &expand[j][i], &expand[j + 1][i], &prev ); + LerpDrawVert( &expand[j + 1][i], &expand[j + 2][i], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = out.height - 1 ; k > j + 3 ; k-- ) { + expand[k][i] = expand[k - 2][i]; + } + expand[j + 1][i] = prev; + expand[j + 2][i] = mid; + expand[j + 3][i] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + // collapse the verts + + out.verts = &expand[0][0]; + for ( i = 1 ; i < out.height ; i++ ) { + memmove( &out.verts[i * out.width], expand[i], out.width * sizeof( bspDrawVert_t ) ); + } + + return CopyMesh( &out ); +} + + + +/* + IterationsForCurve() - ydnar + given a curve of a certain length, return the number of subdivision iterations + note: this is affected by subdivision amount + */ + +int IterationsForCurve( float len, int subdivisions ){ + int iterations, facets; + + + /* calculate the number of subdivisions */ + for ( iterations = 0; iterations < 3; iterations++ ) + { + facets = subdivisions * 16 * pow( 2, iterations ); + if ( facets >= len ) { + break; + } + } + + /* return to caller */ + return iterations; +} + + +/* + SubdivideMesh2() - ydnar + subdivides each mesh quad a specified number of times + */ + +mesh_t *SubdivideMesh2( mesh_t in, int iterations ){ + int i, j, k; + bspDrawVert_t prev, next, mid; + mesh_t out; + + /* ydnar: static for os x */ + MAC_STATIC bspDrawVert_t expand[ MAX_EXPANDED_AXIS ][ MAX_EXPANDED_AXIS ]; + + + /* initial setup */ + out.width = in.width; + out.height = in.height; + out.subdiv_x = in.subdiv_x; + out.subdiv_y = in.subdiv_y; + if (in.subdiv_x == 0 && in.subdiv_y == 0) + { //exact CPs + out.verts = in.verts; + return CopyMesh( &out ); + } + + for ( i = 0; i < in.width; i++ ) + { + for ( j = 0; j < in.height; j++ ) + expand[ j ][ i ] = in.verts[ j * in.width + i ]; + } + if (in.subdiv_x > 0 && in.subdiv_y > 0) + { + } + else + { + /* keep chopping */ + for ( ; iterations > 0; iterations-- ) + { + /* horizontal subdivisions */ + for ( j = 0; j + 2 < out.width; j += 4 ) + { + /* check size limit */ + if ( out.width + 2 >= MAX_EXPANDED_AXIS ) { + break; + } + + /* insert two columns and replace the peak */ + out.width += 2; + for ( i = 0; i < out.height; i++ ) + { + LerpDrawVert( &expand[ i ][ j ], &expand[ i ][ j + 1 ], &prev ); + LerpDrawVert( &expand[ i ][ j + 1 ], &expand[ i ][ j + 2 ], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = out.width - 1 ; k > j + 3; k-- ) + expand [ i ][ k ] = expand[ i ][ k - 2 ]; + expand[ i ][ j + 1 ] = prev; + expand[ i ][ j + 2 ] = mid; + expand[ i ][ j + 3 ] = next; + } + + } + + /* vertical subdivisions */ + for ( j = 0; j + 2 < out.height; j += 4 ) + { + /* check size limit */ + if ( out.height + 2 >= MAX_EXPANDED_AXIS ) { + break; + } + + /* insert two columns and replace the peak */ + out.height += 2; + for ( i = 0; i < out.width; i++ ) + { + LerpDrawVert( &expand[ j ][ i ], &expand[ j + 1 ][ i ], &prev ); + LerpDrawVert( &expand[ j + 1 ][ i ], &expand[ j + 2 ][ i ], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = out.height - 1; k > j + 3; k-- ) + expand[ k ][ i ] = expand[ k - 2 ][ i ]; + expand[ j + 1 ][ i ] = prev; + expand[ j + 2 ][ i ] = mid; + expand[ j + 3 ][ i ] = next; + } + } + } + } + + /* collapse the verts */ + out.verts = &expand[ 0 ][ 0 ]; + for ( i = 1; i < out.height; i++ ) + memmove( &out.verts[ i * out.width ], expand[ i ], out.width * sizeof( bspDrawVert_t ) ); + + /* return to sender */ + return CopyMesh( &out ); +} + + + + + + + +/* + ================ + ProjectPointOntoVector + ================ + */ +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ){ + vec3_t pVec, vec; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec, vec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} + +/* + ================ + RemoveLinearMeshColumsRows + ================ + */ +mesh_t *RemoveLinearMeshColumnsRows( mesh_t *in ) { + int i, j, k; + float len, maxLength; + vec3_t proj, dir; + mesh_t out; + + /* ydnar: static for os x */ + MAC_STATIC bspDrawVert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + + + out.width = in->width; + out.height = in->height; + if (in->subdiv_x >= 0 && in->subdiv_y >= 0) + { //explicit tessellation should not be subject to simplification. + out.verts = in->verts; + return CopyMesh( &out ); + } + + for ( i = 0 ; i < in->width ; i++ ) { + for ( j = 0 ; j < in->height ; j++ ) { + expand[j][i] = in->verts[j * in->width + i]; + } + } + + for ( j = 1 ; j < out.width - 1; j++ ) { + maxLength = 0; + for ( i = 0 ; i < out.height ; i++ ) { + ProjectPointOntoVector( expand[i][j].xyz, expand[i][j - 1].xyz, expand[i][j + 1].xyz, proj ); + VectorSubtract( expand[i][j].xyz, proj, dir ); + len = VectorLength( dir ); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < 0.1 ) { + out.width--; + for ( i = 0 ; i < out.height ; i++ ) { + for ( k = j; k < out.width; k++ ) { + expand[i][k] = expand[i][k + 1]; + } + } + j--; + } + } + for ( j = 1 ; j < out.height - 1; j++ ) { + maxLength = 0; + for ( i = 0 ; i < out.width ; i++ ) { + ProjectPointOntoVector( expand[j][i].xyz, expand[j - 1][i].xyz, expand[j + 1][i].xyz, proj ); + VectorSubtract( expand[j][i].xyz, proj, dir ); + len = VectorLength( dir ); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < 0.1 ) { + out.height--; + for ( i = 0 ; i < out.width ; i++ ) { + for ( k = j; k < out.height; k++ ) { + expand[k][i] = expand[k + 1][i]; + } + } + j--; + } + } + // collapse the verts + out.verts = &expand[0][0]; + for ( i = 1 ; i < out.height ; i++ ) { + memmove( &out.verts[i * out.width], expand[i], out.width * sizeof( bspDrawVert_t ) ); + } + + return CopyMesh( &out ); +} + + + +/* + ================= + SubdivideMeshQuads + ================= + */ +mesh_t *SubdivideMeshQuads( mesh_t *in, float minLength, int maxsize, int *widthtable, int *heighttable ){ + int i, j, k, w, h, maxsubdivisions, subdivisions; + vec3_t dir; + float length, maxLength, amount; + mesh_t out; + bspDrawVert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + + out.width = in->width; + out.height = in->height; + if (in->subdiv_x == 0 && in->subdiv_y == 0) + { //exact CPs + out.verts = in->verts; + return CopyMesh( &out ); + } + + for ( i = 0 ; i < in->width ; i++ ) { + for ( j = 0 ; j < in->height ; j++ ) { + expand[j][i] = in->verts[j * in->width + i]; + } + } + + if ( maxsize > MAX_EXPANDED_AXIS ) { + Error( "SubdivideMeshQuads: maxsize > MAX_EXPANDED_AXIS" ); + } + + // horizontal subdivisions + + maxsubdivisions = ( maxsize - in->width ) / ( in->width - 1 ); + + for ( w = 0, j = 0 ; w < in->width - 1; w++, j += subdivisions + 1 ) { + maxLength = 0; + for ( i = 0 ; i < out.height ; i++ ) { + VectorSubtract( expand[i][j + 1].xyz, expand[i][j].xyz, dir ); + length = VectorLength( dir ); + if ( length > maxLength ) { + maxLength = length; + } + } + + subdivisions = (int) ( maxLength / minLength ); + if ( subdivisions > maxsubdivisions ) { + subdivisions = maxsubdivisions; + } + + widthtable[w] = subdivisions + 1; + if ( subdivisions <= 0 ) { + continue; + } + + out.width += subdivisions; + + for ( i = 0 ; i < out.height ; i++ ) { + for ( k = out.width - 1 ; k > j + subdivisions; k-- ) { + expand[i][k] = expand[i][k - subdivisions]; + } + for ( k = 1; k <= subdivisions; k++ ) + { + amount = (float) k / ( subdivisions + 1 ); + LerpDrawVertAmount( &expand[i][j], &expand[i][j + subdivisions + 1], amount, &expand[i][j + k] ); + } + } + } + + maxsubdivisions = ( maxsize - in->height ) / ( in->height - 1 ); + + for ( h = 0, j = 0 ; h < in->height - 1; h++, j += subdivisions + 1 ) { + maxLength = 0; + for ( i = 0 ; i < out.width ; i++ ) { + VectorSubtract( expand[j + 1][i].xyz, expand[j][i].xyz, dir ); + length = VectorLength( dir ); + if ( length > maxLength ) { + maxLength = length; + } + } + + subdivisions = (int) ( maxLength / minLength ); + if ( subdivisions > maxsubdivisions ) { + subdivisions = maxsubdivisions; + } + + heighttable[h] = subdivisions + 1; + if ( subdivisions <= 0 ) { + continue; + } + + out.height += subdivisions; + + for ( i = 0 ; i < out.width ; i++ ) { + for ( k = out.height - 1 ; k > j + subdivisions; k-- ) { + expand[k][i] = expand[k - subdivisions][i]; + } + for ( k = 1; k <= subdivisions; k++ ) + { + amount = (float) k / ( subdivisions + 1 ); + LerpDrawVertAmount( &expand[j][i], &expand[j + subdivisions + 1][i], amount, &expand[j + k][i] ); + } + } + } + + // collapse the verts + out.verts = &expand[0][0]; + for ( i = 1 ; i < out.height ; i++ ) { + memmove( &out.verts[i * out.width], expand[i], out.width * sizeof( bspDrawVert_t ) ); + } + + return CopyMesh( &out ); +} diff --git a/tools/vmap/model.c b/tools/vmap/model.c new file mode 100644 index 0000000..f07dec6 --- /dev/null +++ b/tools/vmap/model.c @@ -0,0 +1,943 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + +/* marker */ +#define MODEL_C + +/* dependencies */ +#include "vmap.h" + +/* + PicoPrintFunc() + callback for picomodel.lib + */ + +void PicoPrintFunc( int level, const char *str ){ + if ( str == NULL ) { + return; + } + switch ( level ) + { + case PICO_NORMAL: + Sys_Printf( "%s\n", str ); + break; + + case PICO_VERBOSE: + Sys_FPrintf( SYS_VRB, "%s\n", str ); + break; + + case PICO_WARNING: + Sys_FPrintf( SYS_WRN, "WARNING: %s\n", str ); + break; + + case PICO_ERROR: + Sys_FPrintf( SYS_ERR, "ERROR: %s\n", str ); + break; + + case PICO_FATAL: + Error( "ERROR: %s\n", str ); + break; + } +} + + + +/* + PicoLoadFileFunc() + callback for picomodel.lib + */ + +void PicoLoadFileFunc( const char *name, byte **buffer, int *bufSize ){ + *bufSize = vfsLoadFile( name, (void**) buffer, 0 ); +} + + + +/* + FindModel() - ydnar + finds an existing picoModel and returns a pointer to the picoModel_t struct or NULL if not found + */ + +picoModel_t *FindModel( const char *name, int frame ){ + int i; + + + /* init */ + if ( numPicoModels <= 0 ) { + memset( picoModels, 0, sizeof( picoModels ) ); + } + + /* dummy check */ + if ( name == NULL || name[ 0 ] == '\0' ) { + return NULL; + } + + /* search list */ + for ( i = 0; i < MAX_MODELS; i++ ) + { + if ( picoModels[ i ] != NULL && + !strcmp( PicoGetModelName( picoModels[ i ] ), name ) && + PicoGetModelFrameNum( picoModels[ i ] ) == frame ) { + return picoModels[ i ]; + } + } + + /* no matching picoModel found */ + return NULL; +} + + + +/* + LoadModel() - ydnar + loads a picoModel and returns a pointer to the picoModel_t struct or NULL if not found + */ + +picoModel_t *LoadModel( const char *name, int frame ){ + int i; + picoModel_t *model, **pm; + + + /* init */ + if ( numPicoModels <= 0 ) { + memset( picoModels, 0, sizeof( picoModels ) ); + } + + /* dummy check */ + if ( name == NULL || name[ 0 ] == '\0' ) { + return NULL; + } + + /* try to find existing picoModel */ + model = FindModel( name, frame ); + if ( model != NULL ) { + return model; + } + + /* none found, so find first non-null picoModel */ + pm = NULL; + for ( i = 0; i < MAX_MODELS; i++ ) + { + if ( picoModels[ i ] == NULL ) { + pm = &picoModels[ i ]; + break; + } + } + + /* too many picoModels? */ + if ( pm == NULL ) { + Error( "MAX_MODELS (%d) exceeded, there are too many model files referenced by the map.", MAX_MODELS ); + } + + /* attempt to parse model */ + *pm = PicoLoadModel( name, frame ); + + /* if loading failed, make a bogus model to silence the rest of the warnings */ + if ( *pm == NULL ) { + /* allocate a new model */ + *pm = PicoNewModel(); + if ( *pm == NULL ) { + return NULL; + } + + /* set data */ + PicoSetModelName( *pm, name ); + PicoSetModelFrameNum( *pm, frame ); + } + + /* debug code */ + #if 0 + { + int numSurfaces, numVertexes; + picoSurface_t *ps; + + + Sys_Printf( "Model %s\n", name ); + numSurfaces = PicoGetModelNumSurfaces( *pm ); + for ( i = 0; i < numSurfaces; i++ ) + { + ps = PicoGetModelSurface( *pm, i ); + numVertexes = PicoGetSurfaceNumVertexes( ps ); + Sys_Printf( "Surface %d has %d vertexes\n", i, numVertexes ); + } + } + #endif + + /* set count */ + if ( *pm != NULL ) { + numPicoModels++; + } + + /* return the picoModel */ + return *pm; +} + + + +/* + InsertModel() - ydnar + adds a picomodel into the bsp + */ + +void InsertModel( const char *name, int skin, int frame, m4x4_t transform, remap_t *remap, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale, int lightmapSampleSize, float shadeAngle ){ + int i, j, s, numSurfaces; + m4x4_t identity, nTransform; + picoModel_t *model; + picoShader_t *shader; + picoSurface_t *surface; + shaderInfo_t *si; + mapDrawSurface_t *ds; + bspDrawVert_t *dv; + char *picoShaderName; + char shaderName[ MAX_QPATH ]; + picoVec_t *xyz, *normal, *st; + byte *color; + picoIndex_t *indexes; + remap_t *rm, *glob; + skinfile_t *sf, *sf2; + double normalEpsilon_save; + double distanceEpsilon_save; + char skinfilename[ MAX_QPATH ]; + char *skinfilecontent; + int skinfilesize; + char *skinfileptr, *skinfilenextptr; + + /* get model */ + model = LoadModel( name, frame ); + if ( model == NULL ) { + return; + } + + /* load skin file */ + snprintf( skinfilename, sizeof( skinfilename ), "%s_%d.skin", name, skin ); + skinfilename[sizeof( skinfilename ) - 1] = 0; + skinfilesize = vfsLoadFile( skinfilename, (void**) &skinfilecontent, 0 ); + if ( skinfilesize < 0 && skin != 0 ) { + /* fallback to skin 0 if invalid */ + snprintf( skinfilename, sizeof( skinfilename ), "%s_0.skin", name ); + skinfilename[sizeof( skinfilename ) - 1] = 0; + skinfilesize = vfsLoadFile( skinfilename, (void**) &skinfilecontent, 0 ); + if ( skinfilesize >= 0 ) { + Sys_Printf( "Skin %d of %s does not exist, using 0 instead\n", skin, name ); + } + } + sf = NULL; + if ( skinfilesize >= 0 ) { + Sys_Printf( "Using skin %d of %s\n", skin, name ); + int pos; + for ( skinfileptr = skinfilecontent; *skinfileptr; skinfileptr = skinfilenextptr ) + { + // for fscanf + char format[64]; + + skinfilenextptr = strchr( skinfileptr, '\r' ); + if ( skinfilenextptr ) { + *skinfilenextptr++ = 0; + } + else + { + skinfilenextptr = strchr( skinfileptr, '\n' ); + if ( skinfilenextptr ) { + *skinfilenextptr++ = 0; + } + else{ + skinfilenextptr = skinfileptr + strlen( skinfileptr ); + } + } + + /* create new item */ + sf2 = sf; + sf = safe_malloc( sizeof( *sf ) ); + sf->next = sf2; + + sprintf( format, "replace %%%ds %%%ds", (int)sizeof( sf->name ) - 1, (int)sizeof( sf->to ) - 1 ); + if ( sscanf( skinfileptr, format, sf->name, sf->to ) == 2 ) { + continue; + } + sprintf( format, " %%%d[^, ] ,%%%ds", (int)sizeof( sf->name ) - 1, (int)sizeof( sf->to ) - 1 ); + if ( ( pos = sscanf( skinfileptr, format, sf->name, sf->to ) ) == 2 ) { + continue; + } + + /* invalid input line -> discard sf struct */ + Sys_Printf( "Discarding skin directive in %s: %s\n", skinfilename, skinfileptr ); + free( sf ); + sf = sf2; + } + free( skinfilecontent ); + } + + /* handle null matrix */ + if ( transform == NULL ) { + m4x4_identity( identity ); + transform = identity; + } + + /* hack: Stable-1_2 and trunk have differing row/column major matrix order + this transpose is necessary with Stable-1_2 + uncomment the following line with old m4x4_t (non 1.3/spog_branch) code */ + //% m4x4_transpose( transform ); + + /* create transform matrix for normals */ + memcpy( nTransform, transform, sizeof( m4x4_t ) ); + if ( m4x4_invert( nTransform ) ) { + Sys_FPrintf( SYS_VRB, "WARNING: Can't invert model transform matrix, using transpose instead\n" ); + } + m4x4_transpose( nTransform ); + + /* fix bogus lightmap scale */ + if ( lightmapScale <= 0.0f ) { + lightmapScale = 1.0f; + } + + /* fix bogus shade angle */ + if ( shadeAngle <= 0.0f ) { + shadeAngle = 0.0f; + } + + /* each surface on the model will become a new map drawsurface */ + numSurfaces = PicoGetModelNumSurfaces( model ); + //% Sys_FPrintf( SYS_VRB, "Model %s has %d surfaces\n", name, numSurfaces ); + for ( s = 0; s < numSurfaces; s++ ) + { + /* get surface */ + surface = PicoGetModelSurface( model, s ); + if ( surface == NULL ) { + continue; + } + + /* only handle triangle surfaces initially (fixme: support patches) */ + if ( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) { + continue; + } + + /* get shader name */ + shader = PicoGetSurfaceShader( surface ); + if ( shader == NULL ) { + picoShaderName = ""; + } + else{ + picoShaderName = PicoGetShaderName( shader ); + } + + /* handle .skin file */ + if ( sf ) { + picoShaderName = NULL; + for ( sf2 = sf; sf2 != NULL; sf2 = sf2->next ) + { + if ( !Q_stricmp( surface->name, sf2->name ) ) { + Sys_FPrintf( SYS_VRB, "Skin file: mapping %s to %s\n", surface->name, sf2->to ); + picoShaderName = sf2->to; + break; + } + } + if ( !picoShaderName ) { + Sys_FPrintf( SYS_VRB, "Skin file: not mapping %s\n", surface->name ); + continue; + } + } + + /* handle shader remapping */ + glob = NULL; + for ( rm = remap; rm != NULL; rm = rm->next ) + { + if ( rm->from[ 0 ] == '*' && rm->from[ 1 ] == '\0' ) { + glob = rm; + } + else if ( !Q_stricmp( picoShaderName, rm->from ) ) { + Sys_FPrintf( SYS_VRB, "Remapping %s to %s\n", picoShaderName, rm->to ); + picoShaderName = rm->to; + glob = NULL; + break; + } + } + + if ( glob != NULL ) { + Sys_FPrintf( SYS_VRB, "Globbing %s to %s\n", picoShaderName, glob->to ); + picoShaderName = glob->to; + } + + /* shader renaming for sof2 */ + if ( renameModelShaders ) { + strcpy( shaderName, picoShaderName ); + StripExtension( shaderName ); + if ( spawnFlags & 1 ) { + strcat( shaderName, "_RMG_BSP" ); + } + else{ + strcat( shaderName, "_BSP" ); + } + si = ShaderInfoForShader( shaderName ); + } + else{ + si = ShaderInfoForShader( picoShaderName ); + } + + /* allocate a surface (ydnar: gs mods) */ + ds = AllocDrawSurface( SURFACE_TRIANGLES ); + ds->entityNum = eNum; + ds->castShadows = castShadows; + ds->recvShadows = recvShadows; + + /* set shader */ + ds->shaderInfo = si; + + /* force to meta? */ + if ( ( si != NULL && si->forceMeta ) || ( spawnFlags & 4 ) ) { /* 3rd bit */ + ds->type = SURFACE_FORCED_META; + } + + /* fix the surface's normals (jal: conditioned by shader info) */ + if ( !( spawnFlags & 64 ) && ( shadeAngle == 0.0f || ds->type != SURFACE_FORCED_META ) ) { + PicoFixSurfaceNormals( surface ); + } + + /* set sample size */ + if ( lightmapSampleSize > 0.0f ) { + ds->sampleSize = lightmapSampleSize; + } + + /* set lightmap scale */ + if ( lightmapScale > 0.0f ) { + ds->lightmapScale = lightmapScale; + } + + /* set shading angle */ + if ( shadeAngle > 0.0f ) { + ds->shadeAngleDegrees = shadeAngle; + } + + /* set particulars */ + ds->numVerts = PicoGetSurfaceNumVertexes( surface ); + ds->verts = safe_malloc( ds->numVerts * sizeof( ds->verts[ 0 ] ) ); + memset( ds->verts, 0, ds->numVerts * sizeof( ds->verts[ 0 ] ) ); + + ds->numIndexes = PicoGetSurfaceNumIndexes( surface ); + ds->indexes = safe_malloc( ds->numIndexes * sizeof( ds->indexes[ 0 ] ) ); + memset( ds->indexes, 0, ds->numIndexes * sizeof( ds->indexes[ 0 ] ) ); + + /* copy vertexes */ + for ( i = 0; i < ds->numVerts; i++ ) + { + /* get vertex */ + dv = &ds->verts[ i ]; + + /* xyz and normal */ + xyz = PicoGetSurfaceXYZ( surface, i ); + VectorCopy( xyz, dv->xyz ); + m4x4_transform_point( transform, dv->xyz ); + + normal = PicoGetSurfaceNormal( surface, i ); + VectorCopy( normal, dv->normal ); + m4x4_transform_normal( nTransform, dv->normal ); + VectorNormalize( dv->normal, dv->normal ); + + /* ydnar: tek-fu celshading support for flat shaded shit */ + if ( flat ) { + dv->st[ 0 ] = si->stFlat[ 0 ]; + dv->st[ 1 ] = si->stFlat[ 1 ]; + } + + /* ydnar: gs mods: added support for explicit shader texcoord generation */ + else if ( si->tcGen ) { + /* project the texture */ + dv->st[ 0 ] = DotProduct( si->vecs[ 0 ], dv->xyz ); + dv->st[ 1 ] = DotProduct( si->vecs[ 1 ], dv->xyz ); + } + + /* normal texture coordinates */ + else + { + st = PicoGetSurfaceST( surface, 0, i ); + dv->st[ 0 ] = st[ 0 ]; + dv->st[ 1 ] = st[ 1 ]; + } + + /* set lightmap/color bits */ + color = PicoGetSurfaceColor( surface, 0, i ); + for ( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + dv->lightmap[ j ][ 0 ] = 0.0f; + dv->lightmap[ j ][ 1 ] = 0.0f; + if ( spawnFlags & 32 ) { // spawnflag 32: model color -> alpha hack + dv->color[ j ][ 0 ] = 255.0f; + dv->color[ j ][ 1 ] = 255.0f; + dv->color[ j ][ 2 ] = 255.0f; + dv->color[ j ][ 3 ] = RGBTOGRAY( color ); + } + else + { + dv->color[ j ][ 0 ] = color[ 0 ]; + dv->color[ j ][ 1 ] = color[ 1 ]; + dv->color[ j ][ 2 ] = color[ 2 ]; + dv->color[ j ][ 3 ] = color[ 3 ]; + } + } + } + + /* copy indexes */ + indexes = PicoGetSurfaceIndexes( surface, 0 ); + for ( i = 0; i < ds->numIndexes; i++ ) + ds->indexes[ i ] = indexes[ i ]; + + /* set cel shader */ + ds->celShader = celShader; + + /* ydnar: giant hack land: generate clipping brushes for model triangles */ + if ( si->clipModel || ( spawnFlags & 2 ) ) { /* 2nd bit */ + vec3_t points[ 4 ], backs[ 3 ]; + vec4_t plane, reverse, pa, pb, pc; + + + /* temp hack */ + if ( !si->clipModel && !( si->compileFlags & C_SOLID ) ) { + continue; + } + + /* walk triangle list */ + for ( i = 0; i < ds->numIndexes; i += 3 ) + { + /* overflow hack */ + AUTOEXPAND_BY_REALLOC( mapplanes, ( nummapplanes + 64 ) << 1, allocatedmapplanes, 1024 ); + + /* make points and back points */ + for ( j = 0; j < 3; j++ ) + { + /* get vertex */ + dv = &ds->verts[ ds->indexes[ i + j ] ]; + + /* copy xyz */ + VectorCopy( dv->xyz, points[ j ] ); + } + + VectorCopy( points[0], points[3] ); // for cyclic usage + + /* make plane for triangle */ + // div0: add some extra spawnflags: + // 0: snap normals to axial planes for extrusion + // 8: extrude with the original normals + // 16: extrude only with up/down normals (ideal for terrain) + // 24: extrude by distance zero (may need engine changes) + if ( PlaneFromPoints( plane, points[ 0 ], points[ 1 ], points[ 2 ] ) ) { + vec3_t bestNormal; + float backPlaneDistance = 2; + + if ( spawnFlags & 8 ) { // use a DOWN normal + if ( spawnFlags & 16 ) { + // 24: normal as is, and zero width (broken) + VectorCopy( plane, bestNormal ); + } + else + { + // 8: normal as is + VectorCopy( plane, bestNormal ); + } + } + else + { + if ( spawnFlags & 16 ) { + // 16: UP/DOWN normal + VectorSet( bestNormal, 0, 0, ( plane[2] >= 0 ? 1 : -1 ) ); + } + else + { + // 0: axial normal + if ( fabs( plane[0] ) > fabs( plane[1] ) ) { // x>y + if ( fabs( plane[1] ) > fabs( plane[2] ) ) { // x>y, y>z + VectorSet( bestNormal, ( plane[0] >= 0 ? 1 : -1 ), 0, 0 ); + } + else // x>y, z>=y + if ( fabs( plane[0] ) > fabs( plane[2] ) ) { // x>z, z>=y + VectorSet( bestNormal, ( plane[0] >= 0 ? 1 : -1 ), 0, 0 ); + } + else{ // z>=x, x>y + VectorSet( bestNormal, 0, 0, ( plane[2] >= 0 ? 1 : -1 ) ); + } + } + else // y>=x + if ( fabs( plane[1] ) > fabs( plane[2] ) ) { // y>z, y>=x + VectorSet( bestNormal, 0, ( plane[1] >= 0 ? 1 : -1 ), 0 ); + } + else{ // z>=y, y>=x + VectorSet( bestNormal, 0, 0, ( plane[2] >= 0 ? 1 : -1 ) ); + } + } + } + + /* build a brush */ + buildBrush = AllocBrush( 48 ); + buildBrush->entityNum = mapEntityNum; + buildBrush->original = buildBrush; + buildBrush->contentShader = si; + buildBrush->compileFlags = si->compileFlags; + buildBrush->contentFlags = si->contentFlags; + normalEpsilon_save = normalEpsilon; + distanceEpsilon_save = distanceEpsilon; + if ( si->compileFlags & C_STRUCTURAL ) { // allow forced structural brushes here + buildBrush->detail = qfalse; + + // only allow EXACT matches when snapping for these (this is mostly for caulk brushes inside a model) + if ( normalEpsilon > 0 ) { + normalEpsilon = 0; + } + if ( distanceEpsilon > 0 ) { + distanceEpsilon = 0; + } + } + else{ + buildBrush->detail = qtrue; + } + + /* regenerate back points */ + for ( j = 0; j < 3; j++ ) + { + /* get vertex */ + dv = &ds->verts[ ds->indexes[ i + j ] ]; + + // shift by some units + VectorMA( dv->xyz, -64.0f, bestNormal, backs[j] ); // 64 prevents roundoff errors a bit + } + + /* make back plane */ + VectorScale( plane, -1.0f, reverse ); + reverse[ 3 ] = -plane[ 3 ]; + if ( ( spawnFlags & 24 ) != 24 ) { + reverse[3] += DotProduct( bestNormal, plane ) * backPlaneDistance; + } + // that's at least sqrt(1/3) backPlaneDistance, unless in DOWN mode; in DOWN mode, we are screwed anyway if we encounter a plane that's perpendicular to the xy plane) + + if ( PlaneFromPoints( pa, points[ 2 ], points[ 1 ], backs[ 1 ] ) && + PlaneFromPoints( pb, points[ 1 ], points[ 0 ], backs[ 0 ] ) && + PlaneFromPoints( pc, points[ 0 ], points[ 2 ], backs[ 2 ] ) ) { + /* set up brush sides */ + buildBrush->numsides = 5; + buildBrush->sides[ 0 ].shaderInfo = si; + for ( j = 1; j < buildBrush->numsides; j++ ) + buildBrush->sides[ j ].shaderInfo = NULL; // don't emit these faces as draw surfaces, should make smaller BSPs; hope this works + + buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points ); + buildBrush->sides[ 1 ].planenum = FindFloatPlane( pa, pa[ 3 ], 2, &points[ 1 ] ); // pa contains points[1] and points[2] + buildBrush->sides[ 2 ].planenum = FindFloatPlane( pb, pb[ 3 ], 2, &points[ 0 ] ); // pb contains points[0] and points[1] + buildBrush->sides[ 3 ].planenum = FindFloatPlane( pc, pc[ 3 ], 2, &points[ 2 ] ); // pc contains points[2] and points[0] (copied to points[3] + buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 3, backs ); + } + else + { + free( buildBrush ); + continue; + } + + normalEpsilon = normalEpsilon_save; + distanceEpsilon = distanceEpsilon_save; + + /* add to entity */ + if ( CreateBrushWindings( buildBrush ) ) { + AddBrushBevels(); + //% EmitBrushes( buildBrush, NULL, NULL ); + buildBrush->next = entities[ mapEntityNum ].brushes; + entities[ mapEntityNum ].brushes = buildBrush; + entities[ mapEntityNum ].numBrushes++; + } + else{ + free( buildBrush ); + } + } + } + } + } +} + + + +/* + AddTriangleModels() + adds prop_static surfaces to the bsp + */ + +void AddTriangleModels( entity_t *e ){ + int num, frame, skin, castShadows, recvShadows, spawnFlags; + entity_t *e2; + const char *targetName; + const char *target, *model, *value; + char shader[ MAX_QPATH ]; + shaderInfo_t *celShader; + float temp, baseLightmapScale, lightmapScale; + float shadeAngle; + int lightmapSampleSize; + vec3_t origin, scale, angles; + m4x4_t transform; + epair_t *ep; + remap_t *remap, *remap2; + char *split; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- AddTriangleModels ---\n" ); + + /* get current brush entity targetname */ + if ( e == entities ) { + targetName = ""; + } + else + { + targetName = ValueForKey( e, "targetname" ); + + /* prop_static entities target non-worldspawn brush model entities */ + if ( targetName[ 0 ] == '\0' ) { + return; + } + } + + /* get lightmap scale */ + /* vortex: added _ls key (short name of lightmapscale) */ + baseLightmapScale = 0.0f; + if ( strcmp( "", ValueForKey( e, "lightmapscale" ) ) || + strcmp( "", ValueForKey( e, "_lightmapscale" ) ) || + strcmp( "", ValueForKey( e, "_ls" ) ) ) { + baseLightmapScale = FloatForKey( e, "lightmapscale" ); + if ( baseLightmapScale <= 0.0f ) { + baseLightmapScale = FloatForKey( e, "_lightmapscale" ); + } + if ( baseLightmapScale <= 0.0f ) { + baseLightmapScale = FloatForKey( e, "_ls" ); + } + if ( baseLightmapScale < 0.0f ) { + baseLightmapScale = 0.0f; + } + if ( baseLightmapScale > 0.0f ) { + Sys_Printf( "World Entity has lightmap scale of %.4f\n", baseLightmapScale ); + } + } + + /* walk the entity list */ + for ( num = 1; num < numEntities; num++ ) + { + /* get e2 */ + e2 = &entities[ num ]; + + /* convert prop_statics into raw geometry */ + if ( Q_stricmp( "prop_static", ValueForKey( e2, "classname" ) ) && Q_stricmp( "misc_model", ValueForKey( e2, "classname" ) ) ) { + continue; + } + + /* ydnar: added support for md3 models on non-worldspawn models */ + target = ValueForKey( e2, "target" ); + if ( strcmp( target, targetName ) ) { + continue; + } + + /* get model name */ + model = ValueForKey( e2, "model" ); + if ( model[ 0 ] == '\0' ) { + Sys_FPrintf( SYS_WRN, "WARNING: prop_static at %i %i %i without a model key\n", + (int) origin[ 0 ], (int) origin[ 1 ], (int) origin[ 2 ] ); + continue; + } + + /* get model frame */ + frame = 0; + if ( strcmp( "", ValueForKey( e2, "_frame" ) ) ) { + frame = IntForKey( e2, "_frame" ); + } + else if ( strcmp( "", ValueForKey( e2, "frame" ) ) ) { + frame = IntForKey( e2, "frame" ); + } + + /* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */ + if ( e == entities ) { + castShadows = WORLDSPAWN_CAST_SHADOWS; + recvShadows = WORLDSPAWN_RECV_SHADOWS; + } + + /* other entities don't cast any shadows, but recv worldspawn shadows */ + else + { + castShadows = ENTITY_CAST_SHADOWS; + recvShadows = ENTITY_RECV_SHADOWS; + } + + /* get explicit shadow flags */ + GetEntityShadowFlags( e2, e, &castShadows, &recvShadows ); + + /* get spawnflags */ + spawnFlags = IntForKey( e2, "spawnflags" ); + + /* get origin */ + GetVectorForKey( e2, "origin", origin ); + VectorSubtract( origin, e->origin, origin ); /* offset by parent */ + + /* get scale */ + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = 1.0f; + temp = FloatForKey( e2, "modelscale" ); + if ( temp != 0.0f ) { + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp; + } + value = ValueForKey( e2, "modelscale_vec" ); + if ( value[ 0 ] != '\0' ) { + sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] ); + } + + /* get "angle" (yaw) or "angles" (pitch yaw roll) */ + angles[ 0 ] = angles[ 1 ] = angles[ 2 ] = 0.0f; + angles[ 2 ] = FloatForKey( e2, "angle" ); + value = ValueForKey( e2, "angles" ); + if ( value[ 0 ] != '\0' ) { + sscanf( value, "%f %f %f", &angles[ 1 ], &angles[ 2 ], &angles[ 0 ] ); + } + + /* set transform matrix (thanks spog) */ + m4x4_identity( transform ); + m4x4_pivoted_transform_by_vec3( transform, origin, angles, eXYZ, scale, vec3_origin ); + + /* get shader remappings */ + remap = NULL; + for ( ep = e2->epairs; ep != NULL; ep = ep->next ) + { + /* look for keys prefixed with "_remap" */ + if ( ep->key != NULL && ep->value != NULL && + ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' && + !Q_strncasecmp( ep->key, "_remap", 6 ) ) { + /* create new remapping */ + remap2 = remap; + remap = safe_malloc( sizeof( *remap ) ); + remap->next = remap2; + strcpy( remap->from, ep->value ); + + /* split the string */ + split = strchr( remap->from, ';' ); + if ( split == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Shader _remap key found in prop_static without a ; character\n" ); + free( remap ); + remap = remap2; + continue; + } + + /* store the split */ + *split = '\0'; + strcpy( remap->to, ( split + 1 ) ); + + /* note it */ + //% Sys_FPrintf( SYS_VRB, "Remapping %s to %s\n", remap->from, remap->to ); + } + } + + /* ydnar: cel shader support */ + value = ValueForKey( e2, "_celshader" ); + if ( value[ 0 ] == '\0' ) { + value = ValueForKey( &entities[ 0 ], "_celshader" ); + } + if ( value[ 0 ] != '\0' ) { + sprintf( shader, "textures/%s", value ); + celShader = ShaderInfoForShader( shader ); + } + else{ + celShader = *globalCelShader ? ShaderInfoForShader( globalCelShader ) : NULL; + } + + /* jal : entity based _samplesize */ + lightmapSampleSize = 0; + if ( strcmp( "", ValueForKey( e2, "_lightmapsamplesize" ) ) ) { + lightmapSampleSize = IntForKey( e2, "_lightmapsamplesize" ); + } + else if ( strcmp( "", ValueForKey( e2, "_samplesize" ) ) ) { + lightmapSampleSize = IntForKey( e2, "_samplesize" ); + } + + if ( lightmapSampleSize < 0 ) { + lightmapSampleSize = 0; + } + + if ( lightmapSampleSize > 0.0f ) { + Sys_Printf( "prop_static has lightmap sample size of %.d\n", lightmapSampleSize ); + } + + /* get lightmap scale */ + /* vortex: added _ls key (short name of lightmapscale) */ + lightmapScale = 0.0f; + if ( strcmp( "", ValueForKey( e2, "lightmapscale" ) ) || + strcmp( "", ValueForKey( e2, "_lightmapscale" ) ) || + strcmp( "", ValueForKey( e2, "_ls" ) ) ) { + lightmapScale = FloatForKey( e2, "lightmapscale" ); + if ( lightmapScale <= 0.0f ) { + lightmapScale = FloatForKey( e2, "_lightmapscale" ); + } + if ( lightmapScale <= 0.0f ) { + lightmapScale = FloatForKey( e2, "_ls" ); + } + if ( lightmapScale < 0.0f ) { + lightmapScale = 0.0f; + } + if ( lightmapScale > 0.0f ) { + Sys_Printf( "prop_static has lightmap scale of %.4f\n", lightmapScale ); + } + } + + /* jal : entity based _shadeangle */ + shadeAngle = 0.0f; + if ( strcmp( "", ValueForKey( e2, "_shadeangle" ) ) ) { + shadeAngle = FloatForKey( e2, "_shadeangle" ); + } + /* vortex' aliases */ + else if ( strcmp( "", ValueForKey( e2, "_smoothnormals" ) ) ) { + shadeAngle = FloatForKey( e2, "_smoothnormals" ); + } + else if ( strcmp( "", ValueForKey( e2, "_sn" ) ) ) { + shadeAngle = FloatForKey( e2, "_sn" ); + } + else if ( strcmp( "", ValueForKey( e2, "_smooth" ) ) ) { + shadeAngle = FloatForKey( e2, "_smooth" ); + } + + if ( shadeAngle < 0.0f ) { + shadeAngle = 0.0f; + } + + if ( shadeAngle > 0.0f ) { + Sys_Printf( "prop_static has shading angle of %.4f\n", shadeAngle ); + } + + skin = 0; + if ( strcmp( "", ValueForKey( e2, "_skin" ) ) ) { + skin = IntForKey( e2, "_skin" ); + } + else if ( strcmp( "", ValueForKey( e2, "skin" ) ) ) { + skin = IntForKey( e2, "skin" ); + } + + /* insert the model */ + InsertModel( model, skin, frame, transform, remap, celShader, mapEntityNum, castShadows, recvShadows, spawnFlags, lightmapScale, lightmapSampleSize, shadeAngle ); + + /* free shader remappings */ + while ( remap != NULL ) + { + remap2 = remap->next; + free( remap ); + remap = remap2; + } + } +} diff --git a/tools/vmap/patch.c b/tools/vmap/patch.c new file mode 100644 index 0000000..c4635fe --- /dev/null +++ b/tools/vmap/patch.c @@ -0,0 +1,569 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PATCH_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + ExpandLongestCurve() - ydnar + finds length of quadratic curve specified and determines if length is longer than the supplied max + */ + +#define APPROX_SUBDIVISION 8 + +static void ExpandLongestCurve( float *longestCurve, vec3_t a, vec3_t b, vec3_t c ){ + int i; + float t, len; + vec3_t ab, bc, ac, pt, last, delta; + + + /* calc vectors */ + VectorSubtract( b, a, ab ); + if ( VectorNormalize( ab, ab ) < 0.125f ) { + return; + } + VectorSubtract( c, b, bc ); + if ( VectorNormalize( bc, bc ) < 0.125f ) { + return; + } + VectorSubtract( c, a, ac ); + if ( VectorNormalize( ac, ac ) < 0.125f ) { + return; + } + + /* if all 3 vectors are the same direction, then this edge is linear, so we ignore it */ + if ( DotProduct( ab, bc ) > 0.99f && DotProduct( ab, ac ) > 0.99f ) { + return; + } + + /* recalculate vectors */ + VectorSubtract( b, a, ab ); + VectorSubtract( c, b, bc ); + + /* determine length */ + VectorCopy( a, last ); + for ( i = 0, len = 0.0f, t = 0.0f; i < APPROX_SUBDIVISION; i++, t += ( 1.0f / APPROX_SUBDIVISION ) ) + { + /* calculate delta */ + delta[ 0 ] = ( ( 1.0f - t ) * ab[ 0 ] ) + ( t * bc[ 0 ] ); + delta[ 1 ] = ( ( 1.0f - t ) * ab[ 1 ] ) + ( t * bc[ 1 ] ); + delta[ 2 ] = ( ( 1.0f - t ) * ab[ 2 ] ) + ( t * bc[ 2 ] ); + + /* add to first point and calculate pt-pt delta */ + VectorAdd( a, delta, pt ); + VectorSubtract( pt, last, delta ); + + /* add it to length and store last point */ + len += VectorLength( delta ); + VectorCopy( pt, last ); + } + + /* longer? */ + if ( len > *longestCurve ) { + *longestCurve = len; + } +} + + + +/* + ExpandMaxIterations() - ydnar + determines how many iterations a quadratic curve needs to be subdivided with to fit the specified error + */ + +static void ExpandMaxIterations( int *maxIterations, int maxError, vec3_t a, vec3_t b, vec3_t c ){ + int i, j; + vec3_t prev, next, mid, delta, delta2; + float len, len2; + int numPoints, iterations; + vec3_t points[ MAX_EXPANDED_AXIS ]; + + + /* initial setup */ + numPoints = 3; + VectorCopy( a, points[ 0 ] ); + VectorCopy( b, points[ 1 ] ); + VectorCopy( c, points[ 2 ] ); + + /* subdivide */ + for ( i = 0; i + 2 < numPoints; i += 2 ) + { + /* check subdivision limit */ + if ( numPoints + 2 >= MAX_EXPANDED_AXIS ) { + break; + } + + /* calculate new curve deltas */ + for ( j = 0; j < 3; j++ ) + { + prev[ j ] = points[ i + 1 ][ j ] - points[ i ][ j ]; + next[ j ] = points[ i + 2 ][ j ] - points[ i + 1 ][ j ]; + mid[ j ] = ( points[ i ][ j ] + points[ i + 1 ][ j ] * 2.0f + points[ i + 2 ][ j ] ) * 0.25f; + } + + /* see if this midpoint is off far enough to subdivide */ + VectorSubtract( points[ i + 1 ], mid, delta ); + len = VectorLength( delta ); + if ( len < maxError ) { + continue; + } + + /* subdivide */ + numPoints += 2; + + /* create new points */ + for ( j = 0; j < 3; j++ ) + { + prev[ j ] = 0.5f * ( points[ i ][ j ] + points[ i + 1 ][ j ] ); + next[ j ] = 0.5f * ( points[ i + 1 ][ j ] + points[ i + 2 ][ j ] ); + mid[ j ] = 0.5f * ( prev[ j ] + next[ j ] ); + } + + /* push points out */ + for ( j = numPoints - 1; j > i + 3; j-- ) + VectorCopy( points[ j - 2 ], points[ j ] ); + + /* insert new points */ + VectorCopy( prev, points[ i + 1 ] ); + VectorCopy( mid, points[ i + 2 ] ); + VectorCopy( next, points[ i + 3 ] ); + + /* back up and recheck this set again, it may need more subdivision */ + i -= 2; + } + + /* put the line on the curve */ + for ( i = 1; i < numPoints; i += 2 ) + { + for ( j = 0; j < 3; j++ ) + { + prev[ j ] = 0.5f * ( points[ i ][ j ] + points[ i + 1 ][ j ] ); + next[ j ] = 0.5f * ( points[ i ][ j ] + points[ i - 1 ][ j ] ); + points[ i ][ j ] = 0.5f * ( prev[ j ] + next[ j ] ); + } + } + + /* eliminate linear sections */ + for ( i = 0; i + 2 < numPoints; i++ ) + { + /* create vectors */ + VectorSubtract( points[ i + 1 ], points[ i ], delta ); + len = VectorNormalize( delta, delta ); + VectorSubtract( points[ i + 2 ], points[ i + 1 ], delta2 ); + len2 = VectorNormalize( delta2, delta2 ); + + /* if either edge is degenerate, then eliminate it */ + if ( len < 0.0625f || len2 < 0.0625f || DotProduct( delta, delta2 ) >= 1.0f ) { + for ( j = i + 1; j + 1 < numPoints; j++ ) + VectorCopy( points[ j + 1 ], points[ j ] ); + numPoints--; + continue; + } + } + + /* the number of iterations is 2^(points - 1) - 1 */ + numPoints >>= 1; + iterations = 0; + while ( numPoints > 1 ) + { + numPoints >>= 1; + iterations++; + } + + /* more? */ + if ( iterations > *maxIterations ) { + *maxIterations = iterations; + } +} + + +void ParseVertMatrix(bspDrawVert_t *v) +{ + vec4_t vcol; + int i; + MatchToken( "(" ); + + for ( i = 0 ; i < 3 ; i++ ) { + GetToken( qfalse ); + v->xyz[i] = atof( token ); + } + for ( i = 0 ; i < 2 ; i++ ) { + GetToken( qfalse ); + v->st[i] = atof( token ); + } + for ( i = 0 ; i < 4 ; i++ ) { + GetToken( qfalse ); + if (!strcmp(token, ")")) + break; + vcol[i] = atof( token ); + } + for ( ; i < 4 ; i++ ) { + vcol[i] = 1; + } + if (strcmp(token, ")")) + MatchToken( ")" ); + + /* ydnar: fix colors */ + for ( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + v->color[ i ][ 0 ] = 255*vcol[0]; + v->color[ i ][ 1 ] = 255*vcol[1]; + v->color[ i ][ 2 ] = 255*vcol[2]; + v->color[ i ][ 3 ] = 255*vcol[3]; + } +} + +/* + ParsePatch() + creates a mapDrawSurface_t from the patch text + */ + +void ParsePatch( qboolean onlyLights, qboolean fixedtess ){ + vec_t info[ 5 ]; + int i, j; + parseMesh_t *pm; + char texture[ MAX_QPATH ]; + char shader[ MAX_QPATH ]; + mesh_t m; + bspDrawVert_t *verts; + epair_t *ep; + vec4_t delta, delta2, delta3; + qboolean degenerate; + float longestCurve; + int maxIterations; + + MatchToken( "{" ); + + /* get texture */ + GetToken( qtrue ); + strcpy( texture, token ); + + if (fixedtess) + Parse1DMatrix( 7, info ); + else + Parse1DMatrix( 5, info ); + +//Hack for fixed tessellation +/* info[2] = info[3] = 2; + fixedtess = qtrue;*/ + + m.width = info[0]; + m.height = info[1]; + m.subdiv_x = fixedtess?info[2]:-1; + m.subdiv_y = fixedtess?info[3]:-1; + m.verts = verts = safe_malloc( m.width * m.height * sizeof( m.verts[0] ) ); + + if ( m.width < 0 || m.width > MAX_PATCH_SIZE || m.height < 0 || m.height > MAX_PATCH_SIZE ) { + Error( "ParsePatch: bad size" ); + } + + MatchToken( "(" ); + for ( j = 0; j < m.width ; j++ ) + { + MatchToken( "(" ); + for ( i = 0; i < m.height ; i++ ) + ParseVertMatrix(&verts[ i * m.width + j ]); + MatchToken( ")" ); + } + MatchToken( ")" ); + + // if brush primitives format, we may have some epairs to ignore here + GetToken( qtrue ); + if ( g_bBrushPrimit != BPRIMIT_OLDBRUSHES && strcmp( token,"}" ) ) { + ep = ParseEPair(); + free( ep->key ); + free( ep->value ); + free( ep ); + } + else{ + UnGetToken(); + } + + MatchToken( "}" ); + MatchToken( "}" ); + + /* short circuit */ + if ( noCurveBrushes || onlyLights ) { + return; + } + + + /* ydnar: delete and warn about degenerate patches */ + j = ( m.width * m.height ); + VectorClear( delta ); + delta[ 3 ] = 0; + degenerate = qtrue; + + /* find first valid vector */ + for ( i = 1; i < j && delta[ 3 ] == 0; i++ ) + { + VectorSubtract( m.verts[ 0 ].xyz, m.verts[ i ].xyz, delta ); + delta[ 3 ] = VectorNormalize( delta, delta ); + } + + /* secondary degenerate test */ + if ( delta[ 3 ] == 0 ) { + degenerate = qtrue; + } + else + { + /* if all vectors match this or are zero, then this is a degenerate patch */ + for ( i = 1; i < j && degenerate == qtrue; i++ ) + { + VectorSubtract( m.verts[ 0 ].xyz, m.verts[ i ].xyz, delta2 ); + delta2[ 3 ] = VectorNormalize( delta2, delta2 ); + if ( delta2[ 3 ] != 0 ) { + /* create inverse vector */ + VectorCopy( delta2, delta3 ); + delta3[ 3 ] = delta2[ 3 ]; + VectorInverse( delta3 ); + + /* compare */ + if ( VectorCompare( delta, delta2 ) == qfalse && VectorCompare( delta, delta3 ) == qfalse ) { + degenerate = qfalse; + } + } + } + } + + /* warn and select degenerate patch */ + if ( degenerate ) { + xml_Select( "degenerate patch", mapEnt->mapEntityNum, entitySourceBrushes, qfalse ); + free( m.verts ); + return; + } + + /* find longest curve on the mesh */ + longestCurve = 0.0f; + maxIterations = 0; + if (!fixedtess) + { + for ( j = 0; j + 2 < m.width; j += 2 ) + { + for ( i = 0; i + 2 < m.height; i += 2 ) + { + ExpandLongestCurve( &longestCurve, verts[ i * m.width + j ].xyz, verts[ i * m.width + ( j + 1 ) ].xyz, verts[ i * m.width + ( j + 2 ) ].xyz ); /* row */ + ExpandLongestCurve( &longestCurve, verts[ i * m.width + j ].xyz, verts[ ( i + 1 ) * m.width + j ].xyz, verts[ ( i + 2 ) * m.width + j ].xyz ); /* col */ + ExpandMaxIterations( &maxIterations, patchSubdivisions, verts[ i * m.width + j ].xyz, verts[ i * m.width + ( j + 1 ) ].xyz, verts[ i * m.width + ( j + 2 ) ].xyz ); /* row */ + ExpandMaxIterations( &maxIterations, patchSubdivisions, verts[ i * m.width + j ].xyz, verts[ ( i + 1 ) * m.width + j ].xyz, verts[ ( i + 2 ) * m.width + j ].xyz ); /* col */ + } + } + } + + /* allocate patch mesh */ + pm = safe_malloc( sizeof( *pm ) ); + memset( pm, 0, sizeof( *pm ) ); + + /* ydnar: add entity/brush numbering */ + pm->entityNum = mapEnt->mapEntityNum; + pm->brushNum = entitySourceBrushes; + + /* set shader */ + sprintf( shader, "textures/%s", texture ); + pm->shaderInfo = ShaderInfoForShader( shader ); + + /* set mesh */ + pm->mesh = m; + + /* set longest curve */ + pm->longestCurve = longestCurve; + pm->maxIterations = maxIterations; + + /* link to the entity */ + pm->next = mapEnt->patches; + mapEnt->patches = pm; +} + +/* + GrowGroup_r() + recursively adds patches to a lod group + */ + +static void GrowGroup_r( parseMesh_t *pm, int patchNum, int patchCount, parseMesh_t **meshes, byte *bordering, byte *group ){ + int i; + const byte *row; + + + /* early out check */ + if ( group[ patchNum ] ) { + return; + } + + + /* set it */ + group[ patchNum ] = 1; + row = bordering + patchNum * patchCount; + + /* check maximums */ + if ( meshes[ patchNum ]->longestCurve > pm->longestCurve ) { + pm->longestCurve = meshes[ patchNum ]->longestCurve; + } + if ( meshes[ patchNum ]->maxIterations > pm->maxIterations ) { + pm->maxIterations = meshes[ patchNum ]->maxIterations; + } + + /* walk other patches */ + for ( i = 0; i < patchCount; i++ ) + { + if ( row[ i ] ) { + GrowGroup_r( pm, i, patchCount, meshes, bordering, group ); + } + } +} + + +/* + PatchMapDrawSurfs() + any patches that share an edge need to choose their + level of detail as a unit, otherwise the edges would + pull apart. + */ + +void PatchMapDrawSurfs( entity_t *e ){ + int i, j, k, l, c1, c2; + parseMesh_t *pm; + parseMesh_t *check, *scan; + mapDrawSurface_t *ds; + int patchCount, groupCount; + bspDrawVert_t *v1, *v2; + vec3_t bounds[ 2 ]; + byte *bordering; + + /* ydnar: mac os x fails with these if not static */ + MAC_STATIC parseMesh_t *meshes[ MAX_MAP_DRAW_SURFS ]; + MAC_STATIC qb_t grouped[ MAX_MAP_DRAW_SURFS ]; + MAC_STATIC byte group[ MAX_MAP_DRAW_SURFS ]; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- PatchMapDrawSurfs ---\n" ); + + patchCount = 0; + for ( pm = e->patches ; pm ; pm = pm->next ) { + meshes[patchCount] = pm; + patchCount++; + } + + if ( !patchCount ) { + return; + } + bordering = safe_malloc( patchCount * patchCount ); + memset( bordering, 0, patchCount * patchCount ); + + // build the bordering matrix + for ( k = 0 ; k < patchCount ; k++ ) { + bordering[k * patchCount + k] = 1; + + for ( l = k + 1 ; l < patchCount ; l++ ) { + check = meshes[k]; + scan = meshes[l]; + c1 = scan->mesh.width * scan->mesh.height; + v1 = scan->mesh.verts; + + for ( i = 0 ; i < c1 ; i++, v1++ ) { + c2 = check->mesh.width * check->mesh.height; + v2 = check->mesh.verts; + for ( j = 0 ; j < c2 ; j++, v2++ ) { + if ( fabs( v1->xyz[0] - v2->xyz[0] ) < 1.0 + && fabs( v1->xyz[1] - v2->xyz[1] ) < 1.0 + && fabs( v1->xyz[2] - v2->xyz[2] ) < 1.0 ) { + break; + } + } + if ( j != c2 ) { + break; + } + } + if ( i != c1 ) { + // we have a connection + bordering[k * patchCount + l] = + bordering[l * patchCount + k] = 1; + } + else { + // no connection + bordering[k * patchCount + l] = + bordering[l * patchCount + k] = 0; + } + + } + } + + /* build groups */ + memset( grouped, 0, patchCount ); + groupCount = 0; + for ( i = 0; i < patchCount; i++ ) + { + /* get patch */ + scan = meshes[ i ]; + + /* start a new group */ + if ( !grouped[ i ] ) { + groupCount++; + } + + /* recursively find all patches that belong in the same group */ + memset( group, 0, patchCount ); + GrowGroup_r( scan, i, patchCount, meshes, bordering, group ); + + /* bound them */ + ClearBounds( bounds[ 0 ], bounds[ 1 ] ); + for ( j = 0; j < patchCount; j++ ) + { + if ( group[ j ] ) { + grouped[ j ] = qtrue; + check = meshes[ j ]; + c1 = check->mesh.width * check->mesh.height; + v1 = check->mesh.verts; + for ( k = 0; k < c1; k++, v1++ ) + AddPointToBounds( v1->xyz, bounds[ 0 ], bounds[ 1 ] ); + } + } + + /* debug code */ + //% Sys_Printf( "Longest curve: %f Iterations: %d\n", scan->longestCurve, scan->maxIterations ); + + /* create drawsurf */ + scan->grouped = qtrue; + ds = DrawSurfaceForMesh( e, scan, NULL ); /* ydnar */ + VectorCopy( bounds[ 0 ], ds->bounds[ 0 ] ); + VectorCopy( bounds[ 1 ], ds->bounds[ 1 ] ); + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d patches\n", patchCount ); + Sys_FPrintf( SYS_VRB, "%9d patch LOD groups\n", groupCount ); +} diff --git a/tools/vmap/path_init.c b/tools/vmap/path_init.c new file mode 100644 index 0000000..ed7112c --- /dev/null +++ b/tools/vmap/path_init.c @@ -0,0 +1,643 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PATH_INIT_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* path support */ +#define MAX_BASE_PATHS 10 +#define MAX_GAME_PATHS 10 +#define MAX_PAK_PATHS 200 + +char *homePath; +char installPath[ MAX_OS_PATH ]; + +int numBasePaths; +char *basePaths[ MAX_BASE_PATHS ]; +int numGamePaths; +char *gamePaths[ MAX_GAME_PATHS ]; +int numPakPaths; +char *pakPaths[ MAX_PAK_PATHS ]; +char *homeBasePath = NULL; + + +/* + some of this code is based off the original q3map port from loki + and finds various paths. moved here from bsp.c for clarity. + */ + +/* + PathLokiGetHomeDir() + gets the user's home dir (for ~/.q3a) + */ + +char *LokiGetHomeDir( void ){ + #ifndef Q_UNIX + return NULL; + #else + static char buf[ 4096 ]; + struct passwd pw, *pwp; + char *home; + static char homeBuf[MAX_OS_PATH]; + + + /* get the home environment variable */ + home = getenv( "HOME" ); + + /* look up home dir in password database */ + if(!home) + { + if ( getpwuid_r( getuid(), &pw, buf, sizeof( buf ), &pwp ) == 0 ) { + return pw.pw_dir; + } + } + + snprintf( homeBuf, sizeof( homeBuf ), "%s/.", home ); + + /* return it */ + return homeBuf; + #endif +} + + + +/* + PathLokiInitPaths() + initializes some paths on linux/os x + */ + +void LokiInitPaths( char *argv0 ){ + char *home; + + if ( !homePath ) { + /* get home dir */ + home = LokiGetHomeDir(); + if ( home == NULL ) { + home = "."; + } + + /* set home path */ + homePath = home; + } + else{ + home = homePath; + } + + #ifndef Q_UNIX + /* this is kinda crap, but hey */ + strcpy( installPath, "../" ); + #else + + char temp[ MAX_OS_PATH ]; + char *path; + char *last; + qboolean found; + + + path = getenv( "PATH" ); + + /* do some path divining */ + Q_strncpyz( temp, argv0, sizeof( temp ) ); + if ( strrchr( temp, '/' ) ) { + argv0 = strrchr( argv0, '/' ) + 1; + } + else if ( path ) { + + /* + This code has a special behavior when q3map2 is a symbolic link. + + For each dir in ${PATH} (example: "/usr/bin", "/usr/local/bin" if ${PATH} == "/usr/bin:/usr/local/bin"), + it looks for "${dir}/q3map2" (file exists and is executable), + then it uses "dirname(realpath("${dir}/q3map2"))/../" as installPath. + + So, if "/usr/bin/q3map2" is a symbolic link to "/opt/radiant/tools/q3map2", + it will find the installPath "/usr/share/radiant/", + so q3map2 will look for "/opt/radiant/baseq3" to find paks. + + More precisely, it looks for "${dir}/${argv[0]}", + so if "/usr/bin/q3map2" is a symbolic link to "/opt/radiant/tools/q3map2", + and if "/opt/radiant/tools/q3ma2" is a symbolic link to "/opt/radiant/tools/q3map2.x86_64", + it will use "dirname("/opt/radiant/tools/q3map2.x86_64")/../" as path, + so it will use "/opt/radiant/" as installPath, which will be expanded later to "/opt/radiant/baseq3" to find paks. + */ + + found = qfalse; + last = path; + + /* go through each : segment of path */ + while ( last[ 0 ] != '\0' && found == qfalse ) + { + /* null out temp */ + temp[ 0 ] = '\0'; + + /* find next chunk */ + last = strchr( path, ':' ); + if ( last == NULL ) { + last = path + strlen( path ); + } + + /* found home dir candidate */ + if ( *path == '~' ) { + Q_strncpyz( temp, home, sizeof( temp ) ); + path++; + } + + /* concatenate */ + if ( last > ( path + 1 ) ) { + // +1 hack: Q_strncat calls Q_strncpyz that expects a len including '\0' + // so that extraneous char will be rewritten by '\0', so it's ok. + // Also, in this case this extraneous char is always ':' or '\0', so it's ok. + Q_strncat( temp, sizeof( temp ), path, ( last - path + 1) ); + Q_strcat( temp, sizeof( temp ), "/" ); + } + Q_strcat( temp, sizeof( temp ), argv0 ); + + /* verify the path */ + if ( access( temp, X_OK ) == 0 ) { + found = qtrue; + } + path = last + 1; + } + } + + /* flake */ + if ( realpath( temp, installPath ) ) { + /* + if "q3map2" is "/opt/radiant/tools/q3map2", + installPath is "/opt/radiant" + */ + *( strrchr( installPath, '/' ) ) = '\0'; + *( strrchr( installPath, '/' ) ) = '\0'; + } + #endif +} + + + +/* + CleanPath() - ydnar + cleans a dos path \ -> / + */ + +void CleanPath( char *path ){ + while ( *path ) + { + if ( *path == '\\' ) { + *path = '/'; + } + path++; + } +} + + + +/* + GetGame() - ydnar + gets the game_t based on a -game argument + returns NULL if no match found + */ + +game_t *GetGame( char *arg ){ + int i; + + + /* dummy check */ + if ( arg == NULL || arg[ 0 ] == '\0' ) { + return NULL; + } + + /* joke */ + if ( !Q_stricmp( arg, "quake1" ) || + !Q_stricmp( arg, "quake2" ) || + !Q_stricmp( arg, "unreal" ) || + !Q_stricmp( arg, "ut2k3" ) || + !Q_stricmp( arg, "dn3d" ) || + !Q_stricmp( arg, "dnf" ) || + !Q_stricmp( arg, "hl" ) ) { + Sys_Printf( "April fools, silly rabbit!\n" ); + exit( 0 ); + } + + /* test it */ + i = 0; + while ( games[ i ].arg != NULL ) + { + if ( Q_stricmp( arg, games[ i ].arg ) == 0 ) { + return &games[ i ]; + } + i++; + } + + /* no matching game */ + return NULL; +} + + + +/* + AddBasePath() - ydnar + adds a base path to the list + */ + +void AddBasePath( char *path ){ + /* dummy check */ + if ( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) { + return; + } + + /* add it to the list */ + basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( basePaths[ numBasePaths ], path ); + CleanPath( basePaths[ numBasePaths ] ); + numBasePaths++; +} + + + +/* + AddHomeBasePath() - ydnar + adds a base path to the beginning of the list, prefixed by ~/ + */ + +void AddHomeBasePath( char *path ){ + int i; + char temp[ MAX_OS_PATH ]; + int homePathLen; + + if ( !homePath ) { + return; + } + + /* dummy check */ + if ( path == NULL || path[ 0 ] == '\0' ) { + return; + } + + /* strip leading dot, if homePath does not end in /. */ + homePathLen = strlen( homePath ); + if ( !strcmp( path, "." ) ) { + /* -fs_homebase . means that -fs_home is to be used as is */ + strcpy( temp, homePath ); + } + else if ( homePathLen >= 2 && !strcmp( homePath + homePathLen - 2, "/." ) ) { + /* remove trailing /. of homePath */ + homePathLen -= 2; + + /* concatenate home dir and path */ + sprintf( temp, "%.*s/%s", homePathLen, homePath, path ); + } + else + { + /* remove leading . of path */ + if ( path[0] == '.' ) { + ++path; + } + + /* concatenate home dir and path */ + sprintf( temp, "%s/%s", homePath, path ); + } + + /* make a hole */ + for ( i = ( MAX_BASE_PATHS - 2 ); i >= 0; i-- ) + basePaths[ i + 1 ] = basePaths[ i ]; + + /* add it to the list */ + basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 ); + strcpy( basePaths[ 0 ], temp ); + CleanPath( basePaths[ 0 ] ); + numBasePaths++; +} + + + +/* + AddGamePath() - ydnar + adds a game path to the list + */ + +void AddGamePath( char *path ){ + int i; + + /* dummy check */ + if ( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) { + return; + } + + /* add it to the list */ + gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( gamePaths[ numGamePaths ], path ); + CleanPath( gamePaths[ numGamePaths ] ); + numGamePaths++; + + /* don't add it if it's already there */ + for ( i = 0; i < numGamePaths - 1; i++ ) + { + if ( strcmp( gamePaths[i], gamePaths[numGamePaths - 1] ) == 0 ) { + free( gamePaths[numGamePaths - 1] ); + gamePaths[numGamePaths - 1] = NULL; + numGamePaths--; + break; + } + } + +} + + +/* + AddPakPath() + adds a pak path to the list + */ + +void AddPakPath( char *path ){ + /* dummy check */ + if ( path == NULL || path[ 0 ] == '\0' || numPakPaths >= MAX_PAK_PATHS ) { + return; + } + + /* add it to the list */ + pakPaths[ numPakPaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( pakPaths[ numPakPaths ], path ); + CleanPath( pakPaths[ numPakPaths ] ); + numPakPaths++; +} + + + +/* + InitPaths() - ydnar + cleaned up some of the path initialization code from bsp.c + will remove any arguments it uses + */ + +void InitPaths( int *argc, char **argv ){ + int i, j, k, len, len2; + char temp[ MAX_OS_PATH ]; + + int noBasePath = 0; + int noHomePath = 0; + int noMagicPath = 0; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" ); + + /* get the install path for backup */ + LokiInitPaths( argv[ 0 ] ); + + /* set game to default (q3a) */ + game = &games[ 0 ]; + numBasePaths = 0; + numGamePaths = 0; + + /* parse through the arguments and extract those relevant to paths */ + for ( i = 0; i < *argc; i++ ) + { + /* check for null */ + if ( argv[ i ] == NULL ) { + continue; + } + + /* -game */ + if ( strcmp( argv[ i ], "-game" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No game specified after %s", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + game = GetGame( argv[ i ] ); + if ( game == NULL ) { + game = &games[ 0 ]; + } + argv[ i ] = NULL; + } + + /* -fs_forbiddenpath */ + else if ( strcmp( argv[ i ], "-fs_forbiddenpath" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + if ( g_numForbiddenDirs < VFS_MAXDIRS ) { + strncpy( g_strForbiddenDirs[g_numForbiddenDirs], argv[i], PATH_MAX ); + g_strForbiddenDirs[g_numForbiddenDirs][PATH_MAX] = 0; + ++g_numForbiddenDirs; + } + argv[ i ] = NULL; + } + + /* -fs_nobasepath */ + else if ( strcmp( argv[ i ], "-fs_nobasepath" ) == 0 ) { + noBasePath = 1; + // we don't want any basepath, neither guessed ones + noMagicPath = 1; + argv[ i ] = NULL; + } + + /* -fs_nomagicpath */ + else if ( strcmp( argv[ i ], "-fs_nomagicpath") == 0) { + noMagicPath = 1; + argv[ i ] = NULL; + } + + /* -fs_basepath */ + else if ( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + AddBasePath( argv[ i ] ); + argv[ i ] = NULL; + } + + /* -fs_game */ + else if ( strcmp( argv[ i ], "-fs_game" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + AddGamePath( argv[ i ] ); + argv[ i ] = NULL; + } + + /* -fs_home */ + else if ( strcmp( argv[ i ], "-fs_home" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + homePath = argv[i]; + argv[ i ] = NULL; + } + + /* -fs_nohomepath */ + else if ( strcmp( argv[ i ], "-fs_nohomepath" ) == 0 ) { + noHomePath = 1; + argv[ i ] = NULL; + } + + /* -fs_homebase */ + else if ( strcmp( argv[ i ], "-fs_homebase" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + homeBasePath = argv[i]; + argv[ i ] = NULL; + } + + /* -fs_homepath - sets both of them */ + else if ( strcmp( argv[ i ], "-fs_homepath" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + homePath = argv[i]; + homeBasePath = "."; + argv[ i ] = NULL; + } + + /* -fs_pakpath */ + else if ( strcmp( argv[ i ], "-fs_pakpath" ) == 0 ) { + if ( ++i >= *argc ) { + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + } + argv[ i - 1 ] = NULL; + AddPakPath( argv[ i ] ); + argv[ i ] = NULL; + } + } + + /* remove processed arguments */ + for ( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ ) + { + for ( ; j < *argc && argv[ j ] == NULL; j++ ) ; + argv[ i ] = argv[ j ]; + if ( argv[ i ] != NULL ) { + k++; + } + } + *argc = k; + + /* add standard game path */ + AddGamePath( game->gamePath ); + + /* if there is no base path set, figure it out unless fs_nomagicpath is set */ + if ( numBasePaths == 0 && noBasePath == 0 && noMagicPath == 0 ) { + /* this is another crappy replacement for SetQdirFromPath() */ + len2 = strlen( game->magic ); + for ( i = 0; i < *argc && numBasePaths == 0; i++ ) + { + /* extract the arg */ + strcpy( temp, argv[ i ] ); + CleanPath( temp ); + len = strlen( temp ); + Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game->magic, temp, i ); + + /* this is slow, but only done once */ + for ( j = 0; j < ( len - len2 ); j++ ) + { + /* check for the game's magic word */ + if ( Q_strncasecmp( &temp[ j ], game->magic, len2 ) == 0 ) { + /* now find the next slash and nuke everything after it */ + while ( temp[ ++j ] != '/' && temp[ j ] != '\0' ) ; + temp[ j ] = '\0'; + + /* add this as a base path */ + AddBasePath( temp ); + break; + } + } + } + + /* add install path */ + if ( numBasePaths == 0 ) { + AddBasePath( installPath ); + } + + /* check again */ + if ( numBasePaths == 0 ) { + Error( "Failed to find a valid base path." ); + } + } + + if ( noBasePath == 1 ) { + numBasePaths = 0; + } + + if ( noHomePath == 0 ) { + /* this only affects unix */ + if ( homeBasePath ) { + AddHomeBasePath( homeBasePath ); + } + else{ + AddHomeBasePath( game->homeBasePath ); + } + } + + /* initialize vfs paths */ + if ( numBasePaths > MAX_BASE_PATHS ) { + numBasePaths = MAX_BASE_PATHS; + } + if ( numGamePaths > MAX_GAME_PATHS ) { + numGamePaths = MAX_GAME_PATHS; + } + + /* walk the list of game paths */ + for ( j = 0; j < numGamePaths; j++ ) + { + /* walk the list of base paths */ + for ( i = 0; i < numBasePaths; i++ ) + { + /* create a full path and initialize it */ + sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] ); + vfsInitDirectory( temp ); + } + } + + /* initialize vfs paths */ + if ( numPakPaths > MAX_PAK_PATHS ) { + numPakPaths = MAX_PAK_PATHS; + } + + /* walk the list of pak paths */ + for ( i = 0; i < numPakPaths; i++ ) + { + /* initialize this pak path */ + vfsInitDirectory( pakPaths[ i ] ); + } + + /* done */ + Sys_Printf( "\n" ); +} diff --git a/tools/vmap/portals.c b/tools/vmap/portals.c new file mode 100644 index 0000000..0f6975a --- /dev/null +++ b/tools/vmap/portals.c @@ -0,0 +1,1012 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PORTALS_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* ydnar: to fix broken portal windings */ +extern qboolean FixWinding( winding_t *w ); + + +int c_active_portals; +int c_peak_portals; +int c_boundary; +int c_boundary_sides; + +/* + =========== + AllocPortal + =========== + */ +portal_t *AllocPortal( void ){ + portal_t *p; + + if ( numthreads == 1 ) { + c_active_portals++; + } + if ( c_active_portals > c_peak_portals ) { + c_peak_portals = c_active_portals; + } + + p = safe_malloc( sizeof( portal_t ) ); + memset( p, 0, sizeof( portal_t ) ); + + return p; +} + +void FreePortal( portal_t *p ){ + if ( p->winding ) { + FreeWinding( p->winding ); + } + if ( numthreads == 1 ) { + c_active_portals--; + } + free( p ); +} + + + +/* + PortalPassable + returns true if the portal has non-opaque leafs on both sides + */ + +qboolean PortalPassable( portal_t *p ){ + /* is this to global outside leaf? */ + if ( !p->onnode ) { + return qfalse; + } + + /* this should never happen */ + if ( p->nodes[ 0 ]->planenum != PLANENUM_LEAF || + p->nodes[ 1 ]->planenum != PLANENUM_LEAF ) { + Error( "Portal_EntityFlood: not a leaf" ); + } + + /* ydnar: added antiportal to supress portal generation for visibility blocking */ + if ( p->compileFlags & C_ANTIPORTAL ) { + return qfalse; + } + + /* both leaves on either side of the portal must be passable */ + if ( p->nodes[ 0 ]->opaque == qfalse && p->nodes[ 1 ]->opaque == qfalse ) { + return qtrue; + } + + /* otherwise this isn't a passable portal */ + return qfalse; +} + + + + +int c_tinyportals; +int c_badportals; /* ydnar */ + +/* + ============= + AddPortalToNodes + ============= + */ +void AddPortalToNodes( portal_t *p, node_t *front, node_t *back ){ + if ( p->nodes[0] || p->nodes[1] ) { + Error( "AddPortalToNode: allready included" ); + } + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + + +/* + ============= + RemovePortalFromNode + ============= + */ +void RemovePortalFromNode( portal_t *portal, node_t *l ){ + portal_t **pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while ( 1 ) + { + t = *pp; + if ( !t ) { + Error( "RemovePortalFromNode: portal not in leaf" ); + } + + if ( t == portal ) { + break; + } + + if ( t->nodes[0] == l ) { + pp = &t->next[0]; + } + else if ( t->nodes[1] == l ) { + pp = &t->next[1]; + } + else{ + Error( "RemovePortalFromNode: portal not bounding leaf" ); + } + } + + if ( portal->nodes[0] == l ) { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if ( portal->nodes[1] == l ) { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal( portal_t *p ){ + int i; + winding_t *w; + + w = p->winding; + for ( i = 0 ; i < w->numpoints ; i++ ) + Sys_Printf( "(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2] ); +} + +/* + ================ + MakeHeadnodePortals + + The created portals will face the global outside_node + ================ + */ +#define SIDESPACE 8 +void MakeHeadnodePortals( tree_t *tree ){ + vec3_t bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leafs + for ( i = 0 ; i < 3 ; i++ ) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + if ( bounds[0][i] >= bounds[1][i] ) { + Error( "Backwards tree volume" ); + } + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.opaque = qfalse; + + for ( i = 0 ; i < 3 ; i++ ) + for ( j = 0 ; j < 2 ; j++ ) + { + n = j * 3 + i; + + p = AllocPortal(); + portals[n] = p; + + pl = &bplanes[n]; + memset( pl, 0, sizeof( *pl ) ); + if ( j ) { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane( pl->normal, pl->dist ); + AddPortalToNodes( p, node, &tree->outside_node ); + } + +// clip the basewindings by all the other planes + for ( i = 0 ; i < 6 ; i++ ) + { + for ( j = 0 ; j < 6 ; j++ ) + { + if ( j == i ) { + continue; + } + ChopWindingInPlace( &portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON ); + } + } +} + +//=================================================== + + +/* + ================ + BaseWindingForNode + ================ + */ +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode( node_t *node ){ + winding_t *w; + node_t *n; + plane_t *plane; + vec3_t normal; + vec_t dist; + + w = BaseWindingForPlane( mapplanes[node->planenum].normal + , mapplanes[node->planenum].dist ); + + // clip by all the parents + for ( n = node->parent ; n && w ; ) + { + plane = &mapplanes[n->planenum]; + + if ( n->children[0] == node ) { // take front + ChopWindingInPlace( &w, plane->normal, plane->dist, BASE_WINDING_EPSILON ); + } + else + { // take back + VectorSubtract( vec3_origin, plane->normal, normal ); + dist = -plane->dist; + ChopWindingInPlace( &w, normal, dist, BASE_WINDING_EPSILON ); + } + node = n; + n = n->parent; + } + + return w; +} + +//============================================================ + +/* + ================== + MakeNodePortal + + create the new portal by taking the full plane winding for the cutting plane + and clipping it by all of parents of this node + ================== + */ +void MakeNodePortal( node_t *node ){ + portal_t *new_portal, *p; + winding_t *w; + vec3_t normal; + float dist; + int side; + + w = BaseWindingForNode( node ); + + // clip the portal by all the other portals in the node + for ( p = node->portals ; p && w; p = p->next[side] ) + { + if ( p->nodes[0] == node ) { + side = 0; + VectorCopy( p->plane.normal, normal ); + dist = p->plane.dist; + } + else if ( p->nodes[1] == node ) { + side = 1; + VectorSubtract( vec3_origin, p->plane.normal, normal ); + dist = -p->plane.dist; + } + else{ + Error( "CutNodePortals_r: mislinked portal" ); + } + + ChopWindingInPlace( &w, normal, dist, CLIP_EPSILON ); + } + + if ( !w ) { + return; + } + + + /* ydnar: adding this here to fix degenerate windings */ + #if 0 + if ( FixWinding( w ) == qfalse ) { + c_badportals++; + FreeWinding( w ); + return; + } + #endif + + if ( WindingIsTiny( w ) ) { + c_tinyportals++; + FreeWinding( w ); + return; + } + + new_portal = AllocPortal(); + new_portal->plane = mapplanes[node->planenum]; + new_portal->onnode = node; + new_portal->winding = w; + new_portal->compileFlags = node->compileFlags; + AddPortalToNodes( new_portal, node->children[0], node->children[1] ); +} + + +/* + ============== + SplitNodePortals + + Move or split the portals that bound node so that the node's + children have portals instead of node. + ============== + */ +void SplitNodePortals( node_t *node ){ + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for ( p = node->portals ; p ; p = next_portal ) + { + if ( p->nodes[0] == node ) { + side = 0; + } + else if ( p->nodes[1] == node ) { + side = 1; + } + else{ + Error( "SplitNodePortals: mislinked portal" ); + } + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode( p, p->nodes[0] ); + RemovePortalFromNode( p, p->nodes[1] ); + +// +// cut the portal into two portals, one on each side of the cut plane +// + /* not strict, we want to always keep one of them even if coplanar */ + ClipWindingEpsilon( p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding ); + + if ( frontwinding && WindingIsTiny( frontwinding ) ) { + if ( !f->tinyportals ) { + VectorCopy( frontwinding->p[0], f->referencepoint ); + } + f->tinyportals++; + if ( !other_node->tinyportals ) { + VectorCopy( frontwinding->p[0], other_node->referencepoint ); + } + other_node->tinyportals++; + + FreeWinding( frontwinding ); + frontwinding = NULL; + c_tinyportals++; + } + + if ( backwinding && WindingIsTiny( backwinding ) ) { + if ( !b->tinyportals ) { + VectorCopy( backwinding->p[0], b->referencepoint ); + } + b->tinyportals++; + if ( !other_node->tinyportals ) { + VectorCopy( backwinding->p[0], other_node->referencepoint ); + } + other_node->tinyportals++; + + FreeWinding( backwinding ); + backwinding = NULL; + c_tinyportals++; + } + + if ( !frontwinding && !backwinding ) { // tiny windings on both sides + continue; + } + + if ( !frontwinding ) { + FreeWinding( backwinding ); + if ( side == 0 ) { + AddPortalToNodes( p, b, other_node ); + } + else{ + AddPortalToNodes( p, other_node, b ); + } + continue; + } + if ( !backwinding ) { + FreeWinding( frontwinding ); + if ( side == 0 ) { + AddPortalToNodes( p, f, other_node ); + } + else{ + AddPortalToNodes( p, other_node, f ); + } + continue; + } + + // the winding is split + new_portal = AllocPortal(); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding( p->winding ); + p->winding = frontwinding; + + if ( side == 0 ) { + AddPortalToNodes( p, f, other_node ); + AddPortalToNodes( new_portal, b, other_node ); + } + else + { + AddPortalToNodes( p, other_node, f ); + AddPortalToNodes( new_portal, other_node, b ); + } + } + + node->portals = NULL; +} + + +/* + ================ + CalcNodeBounds + ================ + */ +void CalcNodeBounds( node_t *node ){ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + ClearBounds( node->mins, node->maxs ); + for ( p = node->portals ; p ; p = p->next[s] ) + { + s = ( p->nodes[1] == node ); + for ( i = 0 ; i < p->winding->numpoints ; i++ ) + AddPointToBounds( p->winding->p[i], node->mins, node->maxs ); + } +} + +/* + ================== + MakeTreePortals_r + ================== + */ +void MakeTreePortals_r( node_t *node ){ + int i; + + CalcNodeBounds( node ); + if ( node->mins[0] >= node->maxs[0] ) { + Sys_FPrintf( SYS_WRN, "WARNING: node without a volume\n" ); + Sys_Printf( "node has %d tiny portals\n", node->tinyportals ); + Sys_Printf( "node reference point %1.2f %1.2f %1.2f\n", node->referencepoint[0], + node->referencepoint[1], + node->referencepoint[2] ); + } + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( node->mins[i] < MIN_WORLD_COORD || node->maxs[i] > MAX_WORLD_COORD ) { + if ( node->portals && node->portals->winding ) { + xml_Winding( "WARNING: Node With Unbounded Volume", node->portals->winding->p, node->portals->winding->numpoints, qfalse ); + } + + break; + } + } + if ( node->planenum == PLANENUM_LEAF ) { + return; + } + + MakeNodePortal( node ); + SplitNodePortals( node ); + + MakeTreePortals_r( node->children[0] ); + MakeTreePortals_r( node->children[1] ); +} + +/* + ================== + MakeTreePortals + ================== + */ +void MakeTreePortals( tree_t *tree ){ + Sys_FPrintf( SYS_VRB, "--- MakeTreePortals ---\n" ); + MakeHeadnodePortals( tree ); + MakeTreePortals_r( tree->headnode ); + Sys_FPrintf( SYS_VRB, "%9d tiny portals\n", c_tinyportals ); + Sys_FPrintf( SYS_VRB, "%9d bad portals\n", c_badportals ); /* ydnar */ +} + +/* + ========================================================= + + FLOOD ENTITIES + + ========================================================= + */ + +int c_floodedleafs; + +/* + ============= + FloodPortals_r + ============= + */ + +void FloodPortals_r( node_t *node, int dist, qboolean skybox ){ + int s; + portal_t *p; + + + if ( skybox ) { + node->skybox = skybox; + } + + if ( node->opaque ) { + return; + } + + if ( node->occupied ) { + if ( node->occupied > dist ) { + /* reduce distance! */ + /* for better leak line */ + /* note: node->occupied will also be true for all further nodes, then */ + node->occupied = dist; + for ( p = node->portals; p; p = p->next[ s ] ) + { + s = ( p->nodes[ 1 ] == node ); + FloodPortals_r( p->nodes[ !s ], dist + 1, skybox ); + } + } + return; + } + + c_floodedleafs++; + node->occupied = dist; + + for ( p = node->portals; p; p = p->next[ s ] ) + { + s = ( p->nodes[ 1 ] == node ); + FloodPortals_r( p->nodes[ !s ], dist + 1, skybox ); + } +} + + + +/* + ============= + PlaceOccupant + ============= + */ + +qboolean PlaceOccupant( node_t *headnode, vec3_t origin, entity_t *occupant, qboolean skybox ){ + vec_t d; + node_t *node; + plane_t *plane; + + + // find the leaf to start in + node = headnode; + while ( node->planenum != PLANENUM_LEAF ) + { + plane = &mapplanes[ node->planenum ]; + d = DotProduct( origin, plane->normal ) - plane->dist; + if ( d >= 0 ) { + node = node->children[ 0 ]; + } + else{ + node = node->children[ 1 ]; + } + } + + if ( node->opaque ) { + return qfalse; + } + node->occupant = occupant; + node->skybox = skybox; + + FloodPortals_r( node, 1, skybox ); + + return qtrue; +} + +/* + ============= + FloodEntities + + Marks all nodes that can be reached by entites + ============= + */ + +int FloodEntities( tree_t *tree ){ + int i, s; + vec3_t origin, offset, scale, angles; + qboolean r, inside, skybox, found; + node_t *headnode; + entity_t *e, *tripped; + const char *value; + int tripcount; + + + headnode = tree->headnode; + Sys_FPrintf( SYS_VRB,"--- FloodEntities ---\n" ); + inside = qfalse; + tree->outside_node.occupied = 0; + + tripped = qfalse; + c_floodedleafs = 0; + for ( i = 1; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + + /* get origin */ + found = GetVectorForKey( e, "origin", origin ); + + /* as a special case, allow origin-less entities */ + if ( !found ) { + continue; + } + + /* also allow bmodel entities outside, as they could be on a moving path that will go into the map */ + if ( e->brushes != NULL || e->patches != NULL ) { + continue; + } + + /* handle skybox entities */ + value = ValueForKey( e, "classname" ); + if ( !Q_stricmp( value, "_skybox" ) ) { + skybox = qtrue; + skyboxPresent = qtrue; + + /* invert origin */ + VectorScale( origin, -1.0f, offset ); + + /* get scale */ + VectorSet( scale, 64.0f, 64.0f, 64.0f ); + value = ValueForKey( e, "_scale" ); + if ( value[ 0 ] != '\0' ) { + s = sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] ); + if ( s == 1 ) { + scale[ 1 ] = scale[ 0 ]; + scale[ 2 ] = scale[ 0 ]; + } + } + + /* get "angle" (yaw) or "angles" (pitch yaw roll) */ + VectorClear( angles ); + angles[ 2 ] = FloatForKey( e, "angle" ); + value = ValueForKey( e, "angles" ); + if ( value[ 0 ] != '\0' ) { + sscanf( value, "%f %f %f", &angles[ 1 ], &angles[ 2 ], &angles[ 0 ] ); + } + + /* set transform matrix (thanks spog) */ + m4x4_identity( skyboxTransform ); + m4x4_pivoted_transform_by_vec3( skyboxTransform, offset, angles, eXYZ, scale, origin ); + } + else{ + skybox = qfalse; + } + + /* nudge off floor */ + origin[ 2 ] += 1; + + /* debugging code */ + //% if( i == 1 ) + //% origin[ 2 ] += 4096; + + /* find leaf */ + r = PlaceOccupant( headnode, origin, e, skybox ); + if ( r ) { + inside = qtrue; + } + if ( !r ) { + Sys_Printf( "Entity %i, Brush %i: Entity %s in solid at %g %g %g\n", e->mapEntityNum, 0, value, origin[0], origin[1], origin[2] ); + } + else if ( tree->outside_node.occupied ) { + if ( !tripped || tree->outside_node.occupied < tripcount ) { + tripped = e; + tripcount = tree->outside_node.occupied; + } + } + } + + if ( tripped ) { + xml_Select( "Entity leaked", e->mapEntityNum, 0, qfalse ); + } + + Sys_FPrintf( SYS_VRB, "%9d flooded leafs\n", c_floodedleafs ); + + if ( !inside ) { + Sys_FPrintf( SYS_VRB, "no entities in open -- no filling\n" ); + return FLOODENTITIES_EMPTY; + } + if ( tree->outside_node.occupied ) { + Sys_FPrintf( SYS_VRB, "entity reached from outside -- leak detected\n" ); + return FLOODENTITIES_LEAKED; + } + + return FLOODENTITIES_GOOD; +} + +/* + ========================================================= + + FLOOD AREAS + + ========================================================= + */ + +int c_areas; + + + +/* + FloodAreas_r() + floods through leaf portals to tag leafs with an area + */ + +void FloodAreas_r( node_t *node ){ + int s; + portal_t *p; + brush_t *b; + + + if ( node->areaportal ) { + if ( node->area == -1 ) { + node->area = c_areas; + } + + /* this node is part of an area portal brush */ + b = node->brushlist->original; + + /* if the current area has already touched this portal, we are done */ + if ( b->portalareas[ 0 ] == c_areas || b->portalareas[ 1 ] == c_areas ) { + return; + } + + // note the current area as bounding the portal + if ( b->portalareas[ 1 ] != -1 ) { + Sys_FPrintf( SYS_WRN, "WARNING: areaportal brush %i touches > 2 areas\n", b->brushNum ); + return; + } + if ( b->portalareas[ 0 ] != -1 ) { + b->portalareas[ 1 ] = c_areas; + } + else{ + b->portalareas[ 0 ] = c_areas; + } + + return; + } + + if ( node->area != -1 ) { + return; + } + if ( node->cluster == -1 ) { + return; + } + + node->area = c_areas; + + /* ydnar: skybox nodes set the skybox area */ + if ( node->skybox ) { + skyboxArea = c_areas; + } + + for ( p = node->portals; p; p = p->next[ s ] ) + { + s = ( p->nodes[1] == node ); + + /* ydnar: allow areaportal portals to block area flow */ + if ( p->compileFlags & C_AREAPORTAL ) { + continue; + } + + if ( !PortalPassable( p ) ) { + continue; + } + + FloodAreas_r( p->nodes[ !s ] ); + } +} + +/* + ============= + FindAreas_r + + Just decend the tree, and for each node that hasn't had an + area set, flood fill out from there + ============= + */ +void FindAreas_r( node_t *node ){ + if ( node->planenum != PLANENUM_LEAF ) { + FindAreas_r( node->children[ 0 ] ); + FindAreas_r( node->children[ 1 ] ); + return; + } + + if ( node->opaque || node->areaportal || node->area != -1 ) { + return; + } + + FloodAreas_r( node ); + c_areas++; +} + +/* + ============= + CheckAreas_r + ============= + */ +void CheckAreas_r( node_t *node ){ + brush_t *b; + + if ( node->planenum != PLANENUM_LEAF ) { + CheckAreas_r( node->children[0] ); + CheckAreas_r( node->children[1] ); + return; + } + + if ( node->opaque ) { + return; + } + + if ( node->cluster != -1 ) { + if ( node->area == -1 ) { + Sys_FPrintf( SYS_WRN, "WARNING: cluster %d has area set to -1\n", node->cluster ); + } + } + if ( node->areaportal ) { + b = node->brushlist->original; + + // check if the areaportal touches two areas + if ( b->portalareas[0] == -1 || b->portalareas[1] == -1 ) { + Sys_FPrintf( SYS_WRN, "WARNING: areaportal brush %i doesn't touch two areas\n", b->brushNum ); + } + } +} + + + +/* + FloodSkyboxArea_r() - ydnar + sets all nodes with the skybox area to skybox + */ + +void FloodSkyboxArea_r( node_t *node ){ + if ( skyboxArea < 0 ) { + return; + } + + if ( node->planenum != PLANENUM_LEAF ) { + FloodSkyboxArea_r( node->children[ 0 ] ); + FloodSkyboxArea_r( node->children[ 1 ] ); + return; + } + + if ( node->opaque || node->area != skyboxArea ) { + return; + } + + node->skybox = qtrue; +} + + + +/* + FloodAreas() + mark each leaf with an area, bounded by C_AREAPORTAL + */ + +void FloodAreas( tree_t *tree ){ + Sys_FPrintf( SYS_VRB,"--- FloodAreas ---\n" ); + FindAreas_r( tree->headnode ); + + /* ydnar: flood all skybox nodes */ + FloodSkyboxArea_r( tree->headnode ); + + /* check for areaportal brushes that don't touch two areas */ + /* ydnar: fix this rather than just silence the warnings */ + //% CheckAreas_r( tree->headnode ); + + Sys_FPrintf( SYS_VRB, "%9d areas\n", c_areas ); +} + + + +//====================================================== + +int c_outside; +int c_inside; +int c_solid; + +void FillOutside_r( node_t *node ){ + if ( node->planenum != PLANENUM_LEAF ) { + FillOutside_r( node->children[0] ); + FillOutside_r( node->children[1] ); + return; + } + + // anything not reachable by an entity + // can be filled away + if ( !node->occupied ) { + if ( !node->opaque ) { + c_outside++; + node->opaque = qtrue; + } + else { + c_solid++; + } + } + else { + c_inside++; + } + +} + +/* + ============= + FillOutside + + Fill all nodes that can't be reached by entities + ============= + */ +void FillOutside( node_t *headnode ){ + c_outside = 0; + c_inside = 0; + c_solid = 0; + Sys_FPrintf( SYS_VRB,"--- FillOutside ---\n" ); + FillOutside_r( headnode ); + Sys_FPrintf( SYS_VRB,"%9d solid leafs\n", c_solid ); + Sys_Printf( "%9d leafs filled\n", c_outside ); + Sys_FPrintf( SYS_VRB, "%9d inside leafs\n", c_inside ); +} + + +//============================================================== diff --git a/tools/vmap/prtfile.c b/tools/vmap/prtfile.c new file mode 100644 index 0000000..d36dc00 --- /dev/null +++ b/tools/vmap/prtfile.c @@ -0,0 +1,394 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PRTFILE_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + ============================================================================== + + PORTAL FILE GENERATION + + Save out name.prt for qvis to read + ============================================================================== + */ + + +#define PORTALFILE "PRT1" + +FILE *pf; +int num_visclusters; // clusters the player can be in +int num_visportals; +int num_solidfaces; + +void WriteFloat( FILE *f, vec_t v ){ + if ( fabs( v - Q_rint( v ) ) < 0.001 ) { + fprintf( f,"%i ",(int)Q_rint( v ) ); + } + else{ + fprintf( f,"%f ",v ); + } +} + +void CountVisportals_r( node_t *node ){ + int s; + portal_t *p; + winding_t *w; + + // decision node + if ( node->planenum != PLANENUM_LEAF ) { + CountVisportals_r( node->children[0] ); + CountVisportals_r( node->children[1] ); + return; + } + + if ( node->opaque ) { + return; + } + + for ( p = node->portals ; p ; p = p->next[s] ) + { + w = p->winding; + s = ( p->nodes[1] == node ); + if ( w && p->nodes[0] == node ) { + if ( !PortalPassable( p ) ) { + continue; + } + if ( p->nodes[0]->cluster == p->nodes[1]->cluster ) { + continue; + } + ++num_visportals; + } + } +} + +/* + ================= + WritePortalFile_r + ================= + */ +void WritePortalFile_r( node_t *node ){ + int i, s, flags; + portal_t *p; + winding_t *w; + vec3_t normal; + vec_t dist; + + // decision node + if ( node->planenum != PLANENUM_LEAF ) { + WritePortalFile_r( node->children[0] ); + WritePortalFile_r( node->children[1] ); + return; + } + + if ( node->opaque ) { + return; + } + + for ( p = node->portals ; p ; p = p->next[s] ) + { + w = p->winding; + s = ( p->nodes[1] == node ); + if ( w && p->nodes[0] == node ) { + if ( !PortalPassable( p ) ) { + continue; + } + if ( p->nodes[0]->cluster == p->nodes[1]->cluster ) { + continue; + } + --num_visportals; + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane( w, normal, &dist ); + + if ( DotProduct( p->plane.normal, normal ) < 0.99 ) { // backwards... + fprintf( pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster ); + } + else{ + fprintf( pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster ); + } + + flags = 0; + + /* ydnar: added this change to make antiportals work */ + if( p->compileFlags & C_HINT ) { + flags |= 1; + } + + /* divVerent: I want farplanedist to not kill skybox. So... */ + if( p->compileFlags & C_SKY ) { + flags |= 2; + } + + fprintf( pf, "%d ", flags ); + + /* write the winding */ + for ( i = 0 ; i < w->numpoints ; i++ ) + { + fprintf( pf,"(" ); + WriteFloat( pf, w->p[i][0] ); + WriteFloat( pf, w->p[i][1] ); + WriteFloat( pf, w->p[i][2] ); + fprintf( pf,") " ); + } + fprintf( pf,"\n" ); + } + } + +} + +void CountSolidFaces_r( node_t *node ){ + int s; + portal_t *p; + winding_t *w; + + // decision node + if ( node->planenum != PLANENUM_LEAF ) { + CountSolidFaces_r( node->children[0] ); + CountSolidFaces_r( node->children[1] ); + return; + } + + if ( node->opaque ) { + return; + } + + for ( p = node->portals ; p ; p = p->next[s] ) + { + w = p->winding; + s = ( p->nodes[1] == node ); + if ( w ) { + if ( PortalPassable( p ) ) { + continue; + } + if ( p->nodes[0]->cluster == p->nodes[1]->cluster ) { + continue; + } + // write out to the file + + ++num_solidfaces; + } + } +} + +/* + ================= + WriteFaceFile_r + ================= + */ +void WriteFaceFile_r( node_t *node ){ + int i, s; + portal_t *p; + winding_t *w; + + // decision node + if ( node->planenum != PLANENUM_LEAF ) { + WriteFaceFile_r( node->children[0] ); + WriteFaceFile_r( node->children[1] ); + return; + } + + if ( node->opaque ) { + return; + } + + for ( p = node->portals ; p ; p = p->next[s] ) + { + w = p->winding; + s = ( p->nodes[1] == node ); + if ( w ) { + if ( PortalPassable( p ) ) { + continue; + } + if ( p->nodes[0]->cluster == p->nodes[1]->cluster ) { + continue; + } + // write out to the file + + if ( p->nodes[0] == node ) { + fprintf( pf,"%i %i ",w->numpoints, p->nodes[0]->cluster ); + for ( i = 0 ; i < w->numpoints ; i++ ) + { + fprintf( pf,"(" ); + WriteFloat( pf, w->p[i][0] ); + WriteFloat( pf, w->p[i][1] ); + WriteFloat( pf, w->p[i][2] ); + fprintf( pf,") " ); + } + fprintf( pf,"\n" ); + } + else + { + fprintf( pf,"%i %i ",w->numpoints, p->nodes[1]->cluster ); + for ( i = w->numpoints - 1; i >= 0; i-- ) + { + fprintf( pf,"(" ); + WriteFloat( pf, w->p[i][0] ); + WriteFloat( pf, w->p[i][1] ); + WriteFloat( pf, w->p[i][2] ); + fprintf( pf,") " ); + } + fprintf( pf,"\n" ); + } + } + } +} + +/* + ================ + NumberLeafs_r + ================ + */ +void NumberLeafs_r( node_t *node, int c ){ +#if 0 + portal_t *p; +#endif + if ( node->planenum != PLANENUM_LEAF ) { + // decision node + node->cluster = -99; + + if ( node->has_structural_children ) { +#if 0 + if ( c >= 0 ) { + Sys_FPrintf( SYS_ERR,"THIS CANNOT HAPPEN\n" ); + } +#endif + NumberLeafs_r( node->children[0], c ); + NumberLeafs_r( node->children[1], c ); + } + else + { + if ( c < 0 ) { + c = num_visclusters++; + } + NumberLeafs_r( node->children[0], c ); + NumberLeafs_r( node->children[1], c ); + } + return; + } + + node->area = -1; + + if ( node->opaque ) { + // solid block, viewpoint never inside + node->cluster = -1; + return; + } + + if ( c < 0 ) { + c = num_visclusters++; + } + + node->cluster = c; + +#if 0 + // count the portals + for ( p = node->portals ; p ; ) + { + if ( p->nodes[0] == node ) { // only write out from first leaf + if ( PortalPassable( p ) ) { + num_visportals++; + } + else{ + num_solidfaces++; + } + p = p->next[0]; + } + else + { + if ( !PortalPassable( p ) ) { + num_solidfaces++; + } + p = p->next[1]; + } + } +#endif +} + + +/* + ================ + NumberClusters + ================ + */ +void NumberClusters( tree_t *tree ) { + num_visclusters = 0; + num_visportals = 0; + num_solidfaces = 0; + + Sys_FPrintf( SYS_VRB,"--- NumberClusters ---\n" ); + + // set the cluster field in every leaf and count the total number of portals + NumberLeafs_r( tree->headnode, -1 ); + CountVisportals_r( tree->headnode ); + CountSolidFaces_r( tree->headnode ); + + Sys_FPrintf( SYS_VRB, "%9d visclusters\n", num_visclusters ); + Sys_FPrintf( SYS_VRB, "%9d visportals\n", num_visportals ); + Sys_FPrintf( SYS_VRB, "%9d solidfaces\n", num_solidfaces ); +} + +/* + ================ + WritePortalFile + ================ + */ +void WritePortalFile( tree_t *tree, const char *portalFilePath ){ + + Sys_FPrintf( SYS_VRB,"--- WritePortalFile ---\n" ); + + // write the file + Sys_Printf( "writing %s\n", portalFilePath ); + pf = fopen( portalFilePath, "w" ); + if ( !pf ) { + Error( "Error opening %s", portalFilePath ); + } + + fprintf( pf, "%s\n", PORTALFILE ); + fprintf( pf, "%i\n", num_visclusters ); + fprintf( pf, "%i\n", num_visportals ); + fprintf( pf, "%i\n", num_solidfaces ); + + WritePortalFile_r( tree->headnode ); + WriteFaceFile_r( tree->headnode ); + + fclose( pf ); +} diff --git a/tools/vmap/shaders.c b/tools/vmap/shaders.c new file mode 100644 index 0000000..bf5ebd1 --- /dev/null +++ b/tools/vmap/shaders.c @@ -0,0 +1,2152 @@ +/* ------------------------------------------------------------------------------- + + Copyright (C) 1999-2007 id Software, Inc. and contributors. + For a list of contributors, see the accompanying CONTRIBUTORS file. + + This file is part of GtkRadiant. + + GtkRadiant is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + GtkRadiant is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GtkRadiant; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ---------------------------------------------------------------------------------- + + This code has been altered significantly from its original form, to support + several games based on the Quake III Arena engine, in the form of "Q3Map2." + + ------------------------------------------------------------------------------- */ + + + +/* marker */ +#define SHADERS_C + + + +/* dependencies */ +#include "vmap.h" + + + +/* + ColorMod() + routines for dealing with vertex color/alpha modification + */ + +void ColorMod( colorMod_t *cm, int numVerts, bspDrawVert_t *drawVerts ){ + int i, j, k; + float c; + vec4_t mult, add; + bspDrawVert_t *dv; + colorMod_t *cm2; + + + /* dummy check */ + if ( cm == NULL || numVerts < 1 || drawVerts == NULL ) { + return; + } + + + /* walk vertex list */ + for ( i = 0; i < numVerts; i++ ) + { + /* get vertex */ + dv = &drawVerts[ i ]; + + /* walk colorMod list */ + for ( cm2 = cm; cm2 != NULL; cm2 = cm2->next ) + { + /* default */ + VectorSet( mult, 1.0f, 1.0f, 1.0f ); + mult[ 3 ] = 1.0f; + VectorSet( add, 0.0f, 0.0f, 0.0f ); + mult[ 3 ] = 0.0f; + + /* switch on type */ + switch ( cm2->type ) + { + case CM_COLOR_SET: + VectorClear( mult ); + VectorScale( cm2->data, 255.0f, add ); + break; + + case CM_ALPHA_SET: + mult[ 3 ] = 0.0f; + add[ 3 ] = cm2->data[ 0 ] * 255.0f; + break; + + case CM_COLOR_SCALE: + VectorCopy( cm2->data, mult ); + break; + + case CM_ALPHA_SCALE: + mult[ 3 ] = cm2->data[ 0 ]; + break; + + case CM_COLOR_DOT_PRODUCT: + c = DotProduct( dv->normal, cm2->data ); + VectorSet( mult, c, c, c ); + break; + + case CM_COLOR_DOT_PRODUCT_SCALE: + c = DotProduct( dv->normal, cm2->data ); + c = ( c - cm2->data[3] ) / ( cm2->data[4] - cm2->data[3] ); + VectorSet( mult, c, c, c ); + break; + + case CM_ALPHA_DOT_PRODUCT: + mult[ 3 ] = DotProduct( dv->normal, cm2->data ); + break; + + case CM_ALPHA_DOT_PRODUCT_SCALE: + c = DotProduct( dv->normal, cm2->data ); + c = ( c - cm2->data[3] ) / ( cm2->data[4] - cm2->data[3] ); + mult[ 3 ] = c; + break; + + case CM_COLOR_DOT_PRODUCT_2: + c = DotProduct( dv->normal, cm2->data ); + c *= c; + VectorSet( mult, c, c, c ); + break; + + case CM_COLOR_DOT_PRODUCT_2_SCALE: + c = DotProduct( dv->normal, cm2->data ); + c *= c; + c = ( c - cm2->data[3] ) / ( cm2->data[4] - cm2->data[3] ); + VectorSet( mult, c, c, c ); + break; + + case CM_ALPHA_DOT_PRODUCT_2: + mult[ 3 ] = DotProduct( dv->normal, cm2->data ); + mult[ 3 ] *= mult[ 3 ]; + break; + + case CM_ALPHA_DOT_PRODUCT_2_SCALE: + c = DotProduct( dv->normal, cm2->data ); + c *= c; + c = ( c - cm2->data[3] ) / ( cm2->data[4] - cm2->data[3] ); + mult[ 3 ] = c; + break; + + default: + break; + } + + /* apply mod */ + for ( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + for ( k = 0; k < 4; k++ ) + { + c = ( mult[ k ] * dv->color[ j ][ k ] ) + add[ k ]; + if ( c < 0 ) { + c = 0; + } + else if ( c > 255 ) { + c = 255; + } + dv->color[ j ][ k ] = c; + } + } + } + } +} + + + +/* + TCMod*() + routines for dealing with a 3x3 texture mod matrix + */ + +void TCMod( tcMod_t mod, float st[ 2 ] ){ + float old[ 2 ]; + + + old[ 0 ] = st[ 0 ]; + old[ 1 ] = st[ 1 ]; + st[ 0 ] = ( mod[ 0 ][ 0 ] * old[ 0 ] ) + ( mod[ 0 ][ 1 ] * old[ 1 ] ) + mod[ 0 ][ 2 ]; + st[ 1 ] = ( mod[ 1 ][ 0 ] * old[ 0 ] ) + ( mod[ 1 ][ 1 ] * old[ 1 ] ) + mod[ 1 ][ 2 ]; +} + + +void TCModIdentity( tcMod_t mod ){ + mod[ 0 ][ 0 ] = 1.0f; mod[ 0 ][ 1 ] = 0.0f; mod[ 0 ][ 2 ] = 0.0f; + mod[ 1 ][ 0 ] = 0.0f; mod[ 1 ][ 1 ] = 1.0f; mod[ 1 ][ 2 ] = 0.0f; + mod[ 2 ][ 0 ] = 0.0f; mod[ 2 ][ 1 ] = 0.0f; mod[ 2 ][ 2 ] = 1.0f; /* this row is only used for multiples, not transformation */ +} + + +void TCModMultiply( tcMod_t a, tcMod_t b, tcMod_t out ){ + int i; + + + for ( i = 0; i < 3; i++ ) + { + out[ i ][ 0 ] = ( a[ i ][ 0 ] * b[ 0 ][ 0 ] ) + ( a[ i ][ 1 ] * b[ 1 ][ 0 ] ) + ( a[ i ][ 2 ] * b[ 2 ][ 0 ] ); + out[ i ][ 1 ] = ( a[ i ][ 0 ] * b[ 0 ][ 1 ] ) + ( a[ i ][ 1 ] * b[ 1 ][ 1 ] ) + ( a[ i ][ 2 ] * b[ 2 ][ 1 ] ); + out[ i ][ 2 ] = ( a[ i ][ 0 ] * b[ 0 ][ 2 ] ) + ( a[ i ][ 1 ] * b[ 1 ][ 2 ] ) + ( a[ i ][ 2 ] * b[ 2 ][ 2 ] ); + } +} + + +void TCModTranslate( tcMod_t mod, float s, float t ){ + mod[ 0 ][ 2 ] += s; + mod[ 1 ][ 2 ] += t; +} + + +void TCModScale( tcMod_t mod, float s, float t ){ + mod[ 0 ][ 0 ] *= s; + mod[ 1 ][ 1 ] *= t; +} + + +void TCModRotate( tcMod_t mod, float euler ){ + tcMod_t old, temp; + float radians, sinv, cosv; + + + memcpy( old, mod, sizeof( tcMod_t ) ); + TCModIdentity( temp ); + + radians = euler / 180 * Q_PI; + sinv = sin( radians ); + cosv = cos( radians ); + + temp[ 0 ][ 0 ] = cosv; temp[ 0 ][ 1 ] = -sinv; + temp[ 1 ][ 0 ] = sinv; temp[ 1 ][ 1 ] = cosv; + + TCModMultiply( old, temp, mod ); +} + + + +/* + ApplySurfaceParm() - ydnar + applies a named surfaceparm to the supplied flags + */ + +qboolean ApplySurfaceParm( char *name, int *contentFlags, int *surfaceFlags, int *compileFlags ){ + int i, fake; + surfaceParm_t *sp; + + + /* dummy check */ + if ( name == NULL ) { + name = ""; + } + if ( contentFlags == NULL ) { + contentFlags = &fake; + } + if ( surfaceFlags == NULL ) { + surfaceFlags = &fake; + } + if ( compileFlags == NULL ) { + compileFlags = &fake; + } + + /* walk the current game's surfaceparms */ + sp = game->surfaceParms; + while ( sp->name != NULL ) + { + /* match? */ + if ( !Q_stricmp( name, sp->name ) ) { + /* clear and set flags */ + *contentFlags &= ~( sp->contentFlagsClear ); + *contentFlags |= sp->contentFlags; + *surfaceFlags &= ~( sp->surfaceFlagsClear ); + *surfaceFlags |= sp->surfaceFlags; + *compileFlags &= ~( sp->compileFlagsClear ); + *compileFlags |= sp->compileFlags; + + /* return ok */ + return qtrue; + } + + /* next */ + sp++; + } + + /* check custom info parms */ + for ( i = 0; i < numCustSurfaceParms; i++ ) + { + /* get surfaceparm */ + sp = &custSurfaceParms[ i ]; + + /* match? */ + if ( !Q_stricmp( name, sp->name ) ) { + /* clear and set flags */ + *contentFlags &= ~( sp->contentFlagsClear ); + *contentFlags |= sp->contentFlags; + *surfaceFlags &= ~( sp->surfaceFlagsClear ); + *surfaceFlags |= sp->surfaceFlags; + *compileFlags &= ~( sp->compileFlagsClear ); + *compileFlags |= sp->compileFlags; + + /* return ok */ + return qtrue; + } + } + + /* no matching surfaceparm found */ + return qfalse; +} + + + +/* + BeginMapShaderFile() - ydnar + erases and starts a new map shader script + */ + +void BeginMapShaderFile( const char *mapFile ){ + char base[ 1024 ]; + int len; + + + /* dummy check */ + mapName[ 0 ] = '\0'; + mapShaderFile[ 0 ] = '\0'; + if ( mapFile == NULL || mapFile[ 0 ] == '\0' ) { + return; + } + + /* copy map name */ + strcpy( base, mapFile ); + StripExtension( base ); + + /* extract map name */ + len = strlen( base ) - 1; + while ( len > 0 && base[ len ] != '/' && base[ len ] != '\\' ) + len--; + strcpy( mapName, &base[ len + 1 ] ); + base[ len ] = '\0'; + if ( len <= 0 ) { + return; + } + + /* append ../scripts/vmap_.shader */ + sprintf( mapShaderFile, "%s/../%s/vmap_%s.shader", base, game->shaderPath, mapName ); + Sys_FPrintf( SYS_VRB, "Map has shader script %s\n", mapShaderFile ); + + /* remove it */ + remove( mapShaderFile ); + + /* stop making warnings about missing images */ + warnImage = qfalse; +} + + + +/* + WriteMapShaderFile() - ydnar + writes a shader to the map shader script + */ + +void WriteMapShaderFile( void ){ + FILE *file; + shaderInfo_t *si; + int i, num; + + + /* dummy check */ + if ( mapShaderFile[ 0 ] == '\0' ) { + return; + } + + /* are there any custom shaders? */ + for ( i = 0, num = 0; i < numShaderInfo; i++ ) + { + if ( shaderInfo[ i ].custom ) { + break; + } + } + if ( i == numShaderInfo ) { + return; + } + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- WriteMapShaderFile ---\n" ); + Sys_FPrintf( SYS_VRB, "Writing %s", mapShaderFile ); + + /* open shader file */ + file = fopen( mapShaderFile, "w" ); + if ( file == NULL ) { + Sys_FPrintf( SYS_WRN, "WARNING: Unable to open map shader file %s for writing\n", mapShaderFile ); + return; + } + + /* print header */ + fprintf( file, + "// Custom shader file for %s.bsp\n" + "// Generated by Q3Map2 (ydnar)\n" + "// Do not edit! This file is overwritten on recompiles.\n\n", + mapName ); + + /* walk the shader list */ + for ( i = 0, num = 0; i < numShaderInfo; i++ ) + { + /* get the shader and print it */ + si = &shaderInfo[ i ]; + if ( si->custom == qfalse || si->shaderText == NULL || si->shaderText[ 0 ] == '\0' ) { + continue; + } + num++; + + /* print it to the file */ + fprintf( file, "%s%s\n", si->shader, si->shaderText ); + //Sys_Printf( "%s%s\n", si->shader, si->shaderText ); /* FIXME: remove debugging code */ + + Sys_FPrintf( SYS_VRB, "." ); + } + + /* close the shader */ + fflush( file ); + fclose( file ); + + Sys_FPrintf( SYS_VRB, "\n" ); + + /* print some stats */ + Sys_Printf( "%9d custom shaders emitted\n", num ); +} + + + +/* + CustomShader() - ydnar + sets up a custom map shader + */ + +shaderInfo_t *CustomShader( shaderInfo_t *si, char *find, char *replace ){ + shaderInfo_t *csi; + char shader[ MAX_QPATH ]; + char *s; + int loc; + byte digest[ 16 ]; + char *srcShaderText, temp[ 8192 ], shaderText[ 8192 ]; /* ydnar: fixme (make this bigger?) */ + + + /* dummy check */ + if ( si == NULL ) { + return ShaderInfoForShader( "default" ); + } + + /* default shader text source */ + srcShaderText = si->shaderText; + + /* et: implicitMap */ + if ( si->implicitMap == IM_OPAQUE ) { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted (implicitMap)\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t}\n" + "\tq3map_styleMarker\n" + "\t{\n" + "\t\tmap %s\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t\trgbGen identity\n" + "\t}\n" + "}\n", + si->implicitImagePath ); + } + + /* et: implicitMask */ + else if ( si->implicitMap == IM_MASKED ) { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted (implicitMask)\n" + "\tcull none\n" + "\t{\n" + "\t\tmap %s\n" + "\t\talphaFunc GE128\n" + "\t\tdepthWrite\n" + "\t}\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t\tdepthFunc equal\n" + "\t}\n" + "\tq3map_styleMarker\n" + "\t{\n" + "\t\tmap %s\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t\tdepthFunc equal\n" + "\t\trgbGen identity\n" + "\t}\n" + "}\n", + si->implicitImagePath, + si->implicitImagePath ); + } + + /* et: implicitBlend */ + else if ( si->implicitMap == IM_BLEND ) { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted (implicitBlend)\n" + "\tcull none\n" + "\t{\n" + "\t\tmap %s\n" + "\t\tblendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA\n" + "\t}\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t}\n" + "\tq3map_styleMarker\n" + "}\n", + si->implicitImagePath ); + } + + /* default shader text */ + else if ( srcShaderText == NULL ) { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t}\n" + "\tq3map_styleMarker\n" + "\t{\n" + "\t\tmap %s.tga\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t\trgbGen identity\n" + "\t}\n" + "}\n", + si->shader ); + } + + /* error check */ + if ( ( strlen( mapName ) + 1 + 32 ) > MAX_QPATH ) { + Error( "Custom shader name length (%d) exceeded. Shorten your map name.\n", MAX_QPATH ); + } + + /* do some bad find-replace */ + s = strstr( srcShaderText, find ); + if ( s == NULL ) { + //% strcpy( shaderText, srcShaderText ); + return si; /* testing just using the existing shader if this fails */ + } + else + { + /* substitute 'find' with 'replace' */ + loc = s - srcShaderText; + strcpy( shaderText, srcShaderText ); + shaderText[ loc ] = '\0'; + strcat( shaderText, replace ); + strcat( shaderText, &srcShaderText[ loc + strlen( find ) ] ); + } + + /* make md4 hash of the shader text */ + Com_BlockFullChecksum( shaderText, strlen( shaderText ), digest ); + + /* mangle hash into a shader name */ + sprintf( shader, "%s/%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", mapName, + digest[ 0 ], digest[ 1 ], digest[ 2 ], digest[ 3 ], digest[ 4 ], digest[ 5 ], digest[ 6 ], digest[ 7 ], + digest[ 8 ], digest[ 9 ], digest[ 10 ], digest[ 11 ], digest[ 12 ], digest[ 13 ], digest[ 14 ], digest[ 15 ] ); + + /* get shader */ + csi = ShaderInfoForShader( shader ); + + /* might be a preexisting shader */ + if ( csi->custom ) { + return csi; + } + + /* clone the existing shader and rename */ + memcpy( csi, si, sizeof( shaderInfo_t ) ); + strcpy( csi->shader, shader ); + csi->custom = qtrue; + + /* store new shader text */ + csi->shaderText = safe_malloc( strlen( shaderText ) + 1 ); + strcpy( csi->shaderText, shaderText ); /* LEAK! */ + + /* return it */ + return csi; +} + + + +/* + EmitVertexRemapShader() + adds a vertexremapshader key/value pair to worldspawn + */ + +void EmitVertexRemapShader( char *from, char *to ){ + byte digest[ 16 ]; + char key[ 64 ], value[ 256 ]; + + + /* dummy check */ + if ( from == NULL || from[ 0 ] == '\0' || + to == NULL || to[ 0 ] == '\0' ) { + return; + } + + /* build value */ + sprintf( value, "%s;%s", from, to ); + + /* make md4 hash */ + Com_BlockFullChecksum( value, strlen( value ), digest ); + + /* make key (this is annoying, as vertexremapshader is precisely 17 characters, + which is one too long, so we leave off the last byte of the md5 digest) */ + sprintf( key, "vertexremapshader%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + digest[ 0 ], digest[ 1 ], digest[ 2 ], digest[ 3 ], digest[ 4 ], digest[ 5 ], digest[ 6 ], digest[ 7 ], + digest[ 8 ], digest[ 9 ], digest[ 10 ], digest[ 11 ], digest[ 12 ], digest[ 13 ], digest[ 14 ] ); /* no: digest[ 15 ] */ + + /* add key/value pair to worldspawn */ + SetKeyValue( &entities[ 0 ], key, value ); +} + + + +/* + AllocShaderInfo() + allocates and initializes a new shader + */ + +static shaderInfo_t *AllocShaderInfo( void ){ + shaderInfo_t *si; + + + /* allocate? */ + if ( shaderInfo == NULL ) { + shaderInfo = safe_malloc( sizeof( shaderInfo_t ) * MAX_SHADER_INFO ); + numShaderInfo = 0; + } + + /* bounds check */ + if ( numShaderInfo == MAX_SHADER_INFO ) { + Error( "MAX_SHADER_INFO exceeded. Remove some PK3 files or shader scripts from shaderlist.txt and try again." ); + } + si = &shaderInfo[ numShaderInfo ]; + numShaderInfo++; + + /* ydnar: clear to 0 first */ + memset( si, 0, sizeof( shaderInfo_t ) ); + + /* set defaults */ + ApplySurfaceParm( "default", &si->contentFlags, &si->surfaceFlags, &si->compileFlags ); + + si->backsplashFraction = DEF_BACKSPLASH_FRACTION; + si->backsplashDistance = DEF_BACKSPLASH_DISTANCE; + + si->bounceScale = DEF_RADIOSITY_BOUNCE; + + si->lightStyle = LS_NORMAL; + + si->polygonOffset = qfalse; + + si->shadeAngleDegrees = 0.0f; + si->lightmapSampleSize = 0; + si->lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET; + si->patchShadows = qfalse; + si->vertexShadows = qtrue; /* ydnar: changed default behavior */ + si->forceSunlight = qfalse; + si->vertexScale = 1.0; + si->notjunc = qfalse; + + /* ydnar: set texture coordinate transform matrix to identity */ + TCModIdentity( si->mod ); + + /* ydnar: lightmaps can now be > 128x128 in certain games or an externally generated tga */ + si->lmCustomWidth = lmCustomSize; + si->lmCustomHeight = lmCustomSize; + + /* return to sender */ + return si; +} + + + +/* + FinishShader() - ydnar + sets a shader's width and height among other things + */ + +void FinishShader( shaderInfo_t *si ){ + int x, y; + float st[ 2 ], o[ 2 ], dist, bestDist; + vec4_t color, delta; + + + /* don't double-dip */ + if ( si->finished ) { + return; + } + + /* if they're explicitly set, copy from image size */ + if ( si->shaderWidth == 0 && si->shaderHeight == 0 ) { + si->shaderWidth = si->shaderImage->width; + si->shaderHeight = si->shaderImage->height; + } + + /* legacy terrain has explicit image-sized texture projection */ + if ( si->legacyTerrain && si->tcGen == qfalse ) { + /* set xy texture projection */ + si->tcGen = qtrue; + VectorSet( si->vecs[ 0 ], ( 1.0f / ( si->shaderWidth * 0.5f ) ), 0, 0 ); + VectorSet( si->vecs[ 1 ], 0, ( 1.0f / ( si->shaderHeight * 0.5f ) ), 0 ); + } + + /* find pixel coordinates best matching the average color of the image */ + bestDist = 99999999; + o[ 0 ] = 1.0f / si->shaderImage->width; + o[ 1 ] = 1.0f / si->shaderImage->height; + for ( y = 0, st[ 1 ] = 0.0f; y < si->shaderImage->height; y++, st[ 1 ] += o[ 1 ] ) + { + for ( x = 0, st[ 0 ] = 0.0f; x < si->shaderImage->width; x++, st[ 0 ] += o[ 0 ] ) + { + /* sample the shader image */ + RadSampleImage( si->shaderImage->pixels, si->shaderImage->width, si->shaderImage->height, st, color ); + + /* determine error squared */ + VectorSubtract( color, si->averageColor, delta ); + delta[ 3 ] = color[ 3 ] - si->averageColor[ 3 ]; + dist = delta[ 0 ] * delta[ 0 ] + delta[ 1 ] * delta[ 1 ] + delta[ 2 ] * delta[ 2 ] + delta[ 3 ] * delta[ 3 ]; + if ( dist < bestDist ) { + si->stFlat[ 0 ] = st[ 0 ]; + si->stFlat[ 1 ] = st[ 1 ]; + } + } + } + + /* set to finished */ + si->finished = qtrue; +} + + + +/* + LoadShaderImages() + loads a shader's images + ydnar: image.c made this a bit simpler + */ + +static void LoadShaderImages( shaderInfo_t *si ){ + int i, count; + float color[ 4 ]; + + + /* nodraw shaders don't need images */ + if ( si->compileFlags & C_NODRAW ) { + si->shaderImage = ImageLoad( DEFAULT_IMAGE ); + } + else + { + /* try to load editor image first */ + si->shaderImage = ImageLoad( si->editorImagePath ); + + /* then try shadername */ + if ( si->shaderImage == NULL ) { + si->shaderImage = ImageLoad( si->shader ); + } + + /* then try implicit image path (note: new behavior!) */ + if ( si->shaderImage == NULL ) { + si->shaderImage = ImageLoad( si->implicitImagePath ); + } + + /* then try lightimage (note: new behavior!) */ + if ( si->shaderImage == NULL ) { + si->shaderImage = ImageLoad( si->lightImagePath ); + } + + /* otherwise, use default image */ + if ( si->shaderImage == NULL ) { + si->shaderImage = ImageLoad( DEFAULT_IMAGE ); + if ( warnImage && strcmp( si->shader, "noshader" ) ) { + Sys_FPrintf( SYS_WRN, "WARNING: Couldn't find image for shader %s\n", si->shader ); + } + } + + /* load light image */ + si->lightImage = ImageLoad( si->lightImagePath ); + + /* load normalmap image (ok if this is NULL) */ + si->normalImage = ImageLoad( si->normalImagePath ); + if ( si->normalImage != NULL ) { + Sys_FPrintf( SYS_VRB, "Shader %s has\n" + " NM %s\n", si->shader, si->normalImagePath ); + } + } + + /* if no light image, use shader image */ + if ( si->lightImage == NULL ) { + si->lightImage = ImageLoad( si->shaderImage->name ); + } + + /* create default and average colors */ + count = si->lightImage->width * si->lightImage->height; + VectorClear( color ); + color[ 3 ] = 0.0f; + for ( i = 0; i < count; i++ ) + { + color[ 0 ] += si->lightImage->pixels[ i * 4 + 0 ]; + color[ 1 ] += si->lightImage->pixels[ i * 4 + 1 ]; + color[ 2 ] += si->lightImage->pixels[ i * 4 + 2 ]; + color[ 3 ] += si->lightImage->pixels[ i * 4 + 3 ]; + } + + if ( VectorLength( si->color ) <= 0.0f ) { + ColorNormalize( color, si->color ); + VectorScale( color, ( 1.0f / count ), si->averageColor ); + } + else + { + VectorCopy( si->color, si->averageColor ); + } +} + + + +/* + ShaderInfoForShader() + finds a shaderinfo for a named shader + */ + +#define MAX_SHADER_DEPRECATION_DEPTH 16 + +shaderInfo_t *ShaderInfoForShaderNull( const char *shaderName ){ + if ( !strcmp( shaderName, "noshader" ) ) { + return NULL; + } + return ShaderInfoForShader( shaderName ); +} + +shaderInfo_t *ShaderInfoForShader( const char *shaderName ){ + int i; + int deprecationDepth; + shaderInfo_t *si; + char shader[ MAX_QPATH ]; + + /* dummy check */ + if ( shaderName == NULL || shaderName[ 0 ] == '\0' ) { + Sys_FPrintf( SYS_WRN, "WARNING: Null or empty shader name\n" ); + shaderName = "missing"; + } + + /* strip off extension */ + strcpy( shader, shaderName ); + StripExtension( shader ); + + /* search for it */ + deprecationDepth = 0; + for ( i = 0; i < numShaderInfo; i++ ) + { + si = &shaderInfo[ i ]; + if ( !Q_stricmp( shader, si->shader ) ) { + /* check if shader is deprecated */ + if ( deprecationDepth < MAX_SHADER_DEPRECATION_DEPTH && si->deprecateShader && si->deprecateShader[ 0 ] ) { + /* override name */ + strcpy( shader, si->deprecateShader ); + StripExtension( shader ); + /* increase deprecation depth */ + deprecationDepth++; + if ( deprecationDepth == MAX_SHADER_DEPRECATION_DEPTH ) { + Sys_FPrintf( SYS_WRN, "WARNING: Max deprecation depth of %i is reached on shader '%s'\n", MAX_SHADER_DEPRECATION_DEPTH, shader ); + } + /* search again from beginning */ + i = -1; + continue; + } + + /* load image if necessary */ + if ( si->finished == qfalse ) { + LoadShaderImages( si ); + FinishShader( si ); + } + + /* return it */ + return si; + } + } + + /* allocate a default shader */ + si = AllocShaderInfo(); + strcpy( si->shader, shader ); + LoadShaderImages( si ); + FinishShader( si ); + + /* return it */ + return si; +} + + + +/* + GetTokenAppend() - ydnar + gets a token and appends its text to the specified buffer + */ + +static int oldScriptLine = 0; +static int tabDepth = 0; + +qboolean GetTokenAppend( char *buffer, qboolean crossline ){ + qboolean r; + int i; + + + /* get the token */ + r = GetToken( crossline ); + if ( r == qfalse || buffer == NULL || token[ 0 ] == '\0' ) { + return r; + } + + /* pre-tabstops */ + if ( token[ 0 ] == '}' ) { + tabDepth--; + } + + /* append? */ + if ( oldScriptLine != scriptline ) { + strcat( buffer, "\n" ); + for ( i = 0; i < tabDepth; i++ ) + strcat( buffer, "\t" ); + } + else{ + strcat( buffer, " " ); + } + oldScriptLine = scriptline; + strcat( buffer, token ); + + /* post-tabstops */ + if ( token[ 0 ] == '{' ) { + tabDepth++; + } + + /* return */ + return r; +} + + +void Parse1DMatrixAppend( char *buffer, int x, vec_t *m ){ + int i; + + + if ( !GetTokenAppend( buffer, qtrue ) || strcmp( token, "(" ) ) { + Error( "Parse1DMatrixAppend(): line %d: ( not found!", scriptline ); + } + for ( i = 0; i < x; i++ ) + { + if ( !GetTokenAppend( buffer, qfalse ) ) { + Error( "Parse1DMatrixAppend(): line %d: Number not found!", scriptline ); + } + m[ i ] = atof( token ); + } + if ( !GetTokenAppend( buffer, qtrue ) || strcmp( token, ")" ) ) { + Error( "Parse1DMatrixAppend(): line %d: ) not found!", scriptline ); + } +} + + + + +/* + ParseShaderFile() + parses a shader file into discrete shaderInfo_t + */ + +static void ParseShaderFile( const char *filename ){ + int i, val; + shaderInfo_t *si; + char *suffix, temp[ 1024 ]; + char shaderText[ 8192 ]; /* ydnar: fixme (make this bigger?) */ + + + /* init */ + si = NULL; + shaderText[ 0 ] = '\0'; + + /* load the shader */ + LoadScriptFile( filename, 0 ); + + /* tokenize it */ + while ( 1 ) + { + /* copy shader text to the shaderinfo */ + if ( si != NULL && shaderText[ 0 ] != '\0' ) { + strcat( shaderText, "\n" ); + si->shaderText = safe_malloc( strlen( shaderText ) + 1 ); + strcpy( si->shaderText, shaderText ); + //% if( VectorLength( si->vecs[ 0 ] ) ) + //% Sys_Printf( "%s\n", shaderText ); + } + + /* ydnar: clear shader text buffer */ + shaderText[ 0 ] = '\0'; + + /* test for end of file */ + if ( !GetToken( qtrue ) ) { + break; + } + + /* shader name is initial token */ + si = AllocShaderInfo(); + strcpy( si->shader, token ); + + /* ignore ":q3map" suffix */ + suffix = strstr( si->shader, ":q3map" ); + if ( suffix != NULL ) { + *suffix = '\0'; + } + + /* handle { } section */ + if ( !GetTokenAppend( shaderText, qtrue ) ) { + break; + } + if ( strcmp( token, "{" ) ) { + if ( si != NULL ) { + Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s\nLast known shader: %s", + filename, scriptline, token, si->shader ); + } + else{ + Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s", + filename, scriptline, token ); + } + } + + while ( 1 ) + { + /* get the next token */ + if ( !GetTokenAppend( shaderText, qtrue ) ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + + /* ----------------------------------------------------------------- + shader stages (passes) + ----------------------------------------------------------------- */ + + /* parse stage directives */ + if ( !strcmp( token, "{" ) ) { + si->hasPasses = qtrue; + while ( 1 ) + { + if ( !GetTokenAppend( shaderText, qtrue ) ) { + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + + /* only care about images if we don't have a editor/light image */ + if ( si->editorImagePath[ 0 ] == '\0' && si->lightImagePath[ 0 ] == '\0' && si->implicitImagePath[ 0 ] == '\0' ) { + /* digest any images */ + if ( !Q_stricmp( token, "map" ) || + !Q_stricmp( token, "clampMap" ) || + !Q_stricmp( token, "animMap" ) || + !Q_stricmp( token, "clampAnimMap" ) || + !Q_stricmp( token, "clampMap" ) || + !Q_stricmp( token, "mapComp" ) || + !Q_stricmp( token, "mapNoComp" ) ) { + /* skip one token for animated stages */ + if ( !Q_stricmp( token, "animMap" ) || !Q_stricmp( token, "clampAnimMap" ) ) { + GetTokenAppend( shaderText, qfalse ); + } + + /* get an image */ + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] != '*' && token[ 0 ] != '$' ) { + strcpy( si->lightImagePath, token ); + DefaultExtension( si->lightImagePath, ".tga" ); + + /* debug code */ + //% Sys_FPrintf( SYS_VRB, "Deduced shader image: %s\n", si->lightImagePath ); + } + } + } + } + } + + + /* ----------------------------------------------------------------- + surfaceparm * directives + ----------------------------------------------------------------- */ + + /* match surfaceparm */ + else if ( !Q_stricmp( token, "surfaceparm" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( ApplySurfaceParm( token, &si->contentFlags, &si->surfaceFlags, &si->compileFlags ) == qfalse ) { + Sys_FPrintf( SYS_WRN, "WARNING: Unknown surfaceparm: \"%s\"\n", token ); + } + } + + + /* ----------------------------------------------------------------- + game-related shader directives + ----------------------------------------------------------------- */ + + /* ydnar: fogparms (for determining fog volumes) */ + else if ( !Q_stricmp( token, "fogparms" ) ) { + si->fogParms = qtrue; + } + + /* ydnar: polygonoffset (for no culling) */ + else if ( !Q_stricmp( token, "polygonoffset" ) ) { + si->polygonOffset = qtrue; + } + + /* tesssize is used to force liquid surfaces to subdivide */ + else if ( !Q_stricmp( token, "tessSize" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->subdivisions = atof( token ); + } + + /* cull none will set twoSided (ydnar: added disable too) */ + else if ( !Q_stricmp( token, "cull" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "disable" ) || !Q_stricmp( token, "twosided" ) ) { + si->twoSided = qtrue; + } + } + + /* deformVertexes autosprite[ 2 ] + we catch this so autosprited surfaces become point + lights instead of area lights */ + else if ( !Q_stricmp( token, "deformVertexes" ) ) { + GetTokenAppend( shaderText, qfalse ); + + /* deformVertexes autosprite(2) */ + if ( !Q_strncasecmp( token, "autosprite", 10 ) ) { + /* set it as autosprite and detail */ + si->autosprite = qtrue; + ApplySurfaceParm( "detail", &si->contentFlags, &si->surfaceFlags, &si->compileFlags ); + + /* ydnar: gs mods: added these useful things */ + si->noClip = qtrue; + si->notjunc = qtrue; + } + + /* deformVertexes move (ydnar: for particle studio support) */ + if ( !Q_stricmp( token, "move" ) ) { + vec3_t amt, mins, maxs; + float base, amp; + + + /* get move amount */ + GetTokenAppend( shaderText, qfalse ); amt[ 0 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); amt[ 1 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); amt[ 2 ] = atof( token ); + + /* skip func */ + GetTokenAppend( shaderText, qfalse ); + + /* get base and amplitude */ + GetTokenAppend( shaderText, qfalse ); base = atof( token ); + GetTokenAppend( shaderText, qfalse ); amp = atof( token ); + + /* calculate */ + VectorScale( amt, base, mins ); + VectorMA( mins, amp, amt, maxs ); + VectorAdd( si->mins, mins, si->mins ); + VectorAdd( si->maxs, maxs, si->maxs ); + } + } + + /* light (old-style flare specification) */ + else if ( !Q_stricmp( token, "light" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->flareShader = game->flareShader; + } + + /* ydnar: damageShader (sof2 mods) */ + else if ( !Q_stricmp( token, "damageShader" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] != '\0' ) { + si->damageShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->damageShader, token ); + } + GetTokenAppend( shaderText, qfalse ); /* don't do anything with health */ + } + + /* ydnar: enemy territory implicit shaders */ + else if ( !Q_stricmp( token, "implicitMap" ) ) { + si->implicitMap = IM_OPAQUE; + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] == '-' && token[ 1 ] == '\0' ) { + sprintf( si->implicitImagePath, "%s.tga", si->shader ); + } + else{ + strcpy( si->implicitImagePath, token ); + } + } + + else if ( !Q_stricmp( token, "implicitMask" ) ) { + si->implicitMap = IM_MASKED; + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] == '-' && token[ 1 ] == '\0' ) { + sprintf( si->implicitImagePath, "%s.tga", si->shader ); + } + else{ + strcpy( si->implicitImagePath, token ); + } + } + + else if ( !Q_stricmp( token, "implicitBlend" ) ) { + si->implicitMap = IM_MASKED; + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] == '-' && token[ 1 ] == '\0' ) { + sprintf( si->implicitImagePath, "%s.tga", si->shader ); + } + else{ + strcpy( si->implicitImagePath, token ); + } + } + + + /* ----------------------------------------------------------------- + image directives + ----------------------------------------------------------------- */ + + /* qer_editorimage */ + else if ( !Q_stricmp( token, "qer_editorImage" ) ) { + GetTokenAppend( shaderText, qfalse ); + strcpy( si->editorImagePath, token ); + DefaultExtension( si->editorImagePath, ".tga" ); + } + + /* diffusemap */ + else if ( !Q_stricmp( token, "diffusemap" ) ) { + GetTokenAppend( shaderText, qfalse ); + strcpy( si->editorImagePath, token ); + DefaultExtension( si->editorImagePath, ".tga" ); + } + + /* ydnar: q3map_normalimage (bumpmapping normal map) */ + /*else if ( !Q_stricmp( token, "q3map_normalImage" ) ) { + GetTokenAppend( shaderText, qfalse ); + strcpy( si->normalImagePath, token ); + DefaultExtension( si->normalImagePath, ".tga" ); + }*/ + + /* q3map_lightimage */ + else if ( !Q_stricmp( token, "q3map_lightImage" ) || !Q_stricmp( token, "vmap_lightImage" ) ) { + GetTokenAppend( shaderText, qfalse ); + strcpy( si->lightImagePath, token ); + DefaultExtension( si->lightImagePath, ".tga" ); + } + + /* ydnar: skyparms */ + else if ( !Q_stricmp( token, "skyParms" ) ) { + /* get image base */ + GetTokenAppend( shaderText, qfalse ); + + /* ignore bogus paths */ + if ( Q_stricmp( token, "-" ) && Q_stricmp( token, "full" ) ) { + strcpy( si->skyParmsImageBase, token ); + + /* use top image as sky light image */ + if ( si->lightImagePath[ 0 ] == '\0' ) { + sprintf( si->lightImagePath, "%s_up.tga", si->skyParmsImageBase ); + } + } + + /* skip rest of line */ + GetTokenAppend( shaderText, qfalse ); + GetTokenAppend( shaderText, qfalse ); + } + + /* ----------------------------------------------------------------- + q3map_* directives + ----------------------------------------------------------------- */ + + /* q3map_sun + color will be normalized, so it doesn't matter what range you use + intensity falls off with angle but not distance 100 is a fairly bright sun + degree of 0 = from the east, 90 = north, etc. altitude of 0 = sunrise/set, 90 = noon */ +#if 0 + else if ( !Q_stricmp( token, "vmap_sun" ) || !Q_stricmp( token, "q3map_sun" ) || !Q_stricmp( token, "q3map_sunExt" ) ) { + float a, b; + sun_t *sun; + qboolean ext = qfalse; + + /* ydnar: extended sun directive? */ + if ( !Q_stricmp( token, "q3map_sunext" ) || !Q_stricmp( token, "vmap_sun" ) ) { + ext = qtrue; + } + + /* allocate sun */ + sun = safe_malloc( sizeof( *sun ) ); + memset( sun, 0, sizeof( *sun ) ); + + /* set style */ + sun->style = si->lightStyle; + + /* get color */ + GetTokenAppend( shaderText, qfalse ); + sun->color[ 0 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + sun->color[ 1 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + sun->color[ 2 ] = atof( token ); + + if ( colorsRGB ) { + sun->color[0] = Image_LinearFloatFromsRGBFloat( sun->color[0] ); + sun->color[1] = Image_LinearFloatFromsRGBFloat( sun->color[1] ); + sun->color[2] = Image_LinearFloatFromsRGBFloat( sun->color[2] ); + } + + /* normalize it */ + ColorNormalize( sun->color, sun->color ); + + /* scale color by brightness */ + GetTokenAppend( shaderText, qfalse ); + sun->photons = atof( token ); + + /* get sun angle/elevation */ + GetTokenAppend( shaderText, qfalse ); + a = atof( token ); + a = a / 180.0f * Q_PI; + + GetTokenAppend( shaderText, qfalse ); + b = atof( token ); + b = b / 180.0f * Q_PI; + + sun->direction[ 0 ] = cos( a ) * cos( b ); + sun->direction[ 1 ] = sin( a ) * cos( b ); + sun->direction[ 2 ] = sin( b ); + + /* get filter radius from shader */ + sun->filterRadius = si->lightFilterRadius; + + /* ydnar: get sun angular deviance/samples */ + if ( ext && TokenAvailable() ) { + GetTokenAppend( shaderText, qfalse ); + sun->deviance = atof( token ); + sun->deviance = sun->deviance / 180.0f * Q_PI; + + GetTokenAppend( shaderText, qfalse ); + sun->numSamples = atoi( token ); + } + + /* store sun */ + sun->next = si->sun; + si->sun = sun; + + /* apply sky surfaceparm */ + ApplySurfaceParm( "sky", &si->contentFlags, &si->surfaceFlags, &si->compileFlags ); + + /* don't process any more tokens on this line */ + continue; + } +#endif + /* match q3map_ */ + else if ( !Q_strncasecmp( token, "q3map_", 6 ) || !Q_strncasecmp( token, "vmap_", 5 ) ) { + /* ydnar: q3map_baseShader (inherit this shader's parameters) */ + if ( !Q_stricmp( token, "q3map_baseShader" ) || !Q_stricmp( token, "vmap_baseShader" ) ) { + shaderInfo_t *si2; + qboolean oldWarnImage; + + + /* get shader */ + GetTokenAppend( shaderText, qfalse ); + //% Sys_FPrintf( SYS_VRB, "Shader %s has base shader %s\n", si->shader, token ); + oldWarnImage = warnImage; + warnImage = qfalse; + si2 = ShaderInfoForShader( token ); + warnImage = oldWarnImage; + + /* subclass it */ + if ( si2 != NULL ) { + /* preserve name */ + strcpy( temp, si->shader ); + + /* copy shader */ + memcpy( si, si2, sizeof( *si ) ); + + /* restore name and set to unfinished */ + strcpy( si->shader, temp ); + si->shaderWidth = 0; + si->shaderHeight = 0; + si->finished = qfalse; + } + } + + /* ydnar: q3map_surfacemodel */ + else if ( !Q_stricmp( token, "q3map_surfacemodel" ) || !Q_stricmp( token, "vmap_surfacemodel" ) ) { + surfaceModel_t *model; + + /* allocate new model and attach it */ + model = safe_malloc( sizeof( *model ) ); + memset( model, 0, sizeof( *model ) ); + model->next = si->surfaceModel; + si->surfaceModel = model; + + /* get parameters */ + GetTokenAppend( shaderText, qfalse ); + strcpy( model->model, token ); + + GetTokenAppend( shaderText, qfalse ); + model->density = atof( token ); + GetTokenAppend( shaderText, qfalse ); + model->odds = atof( token ); + + GetTokenAppend( shaderText, qfalse ); + model->minScale = atof( token ); + GetTokenAppend( shaderText, qfalse ); + model->maxScale = atof( token ); + + GetTokenAppend( shaderText, qfalse ); + model->minAngle = atof( token ); + GetTokenAppend( shaderText, qfalse ); + model->maxAngle = atof( token ); + + GetTokenAppend( shaderText, qfalse ); + model->oriented = ( token[ 0 ] == '1' ? qtrue : qfalse ); + } + + /* ydnar/sd: q3map_foliage */ + else if ( !Q_stricmp( token, "q3map_foliage" ) || !Q_stricmp( token, "vmap_foliage" ) ) { + foliage_t *foliage; + + + /* allocate new foliage struct and attach it */ + foliage = safe_malloc( sizeof( *foliage ) ); + memset( foliage, 0, sizeof( *foliage ) ); + foliage->next = si->foliage; + si->foliage = foliage; + + /* get parameters */ + GetTokenAppend( shaderText, qfalse ); + strcpy( foliage->model, token ); + + GetTokenAppend( shaderText, qfalse ); + foliage->scale = atof( token ); + GetTokenAppend( shaderText, qfalse ); + foliage->density = atof( token ); + GetTokenAppend( shaderText, qfalse ); + foliage->odds = atof( token ); + GetTokenAppend( shaderText, qfalse ); + foliage->inverseAlpha = atoi( token ); + } + + /* ydnar: q3map_bounce (fraction of light to re-emit during radiosity passes) */ + else if ( !Q_stricmp( token, "q3map_bounce" ) || !Q_stricmp( token, "vmap_bounce" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->bounceScale = atof( token ); + } +#if 0 + /* ydnar/splashdamage: q3map_skyLight */ + else if ( !Q_stricmp( token, "q3map_skyLight" ) || !Q_stricmp( token, "vmap_skyLight" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->skyLightValue = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->skyLightIterations = atoi( token ); + + /* clamp */ + if ( si->skyLightValue < 0.0f ) { + si->skyLightValue = 0.0f; + } + if ( si->skyLightIterations < 2 ) { + si->skyLightIterations = 2; + } + } +#endif + /* q3map_surfacelight */ + else if ( !Q_stricmp( token, "q3map_surfacelight" ) || !Q_stricmp( token, "vmap_surfacelight" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->value = atof( token ); + } + + /* q3map_lightStyle (sof2/jk2 lightstyle) */ + else if ( !Q_stricmp( token, "q3map_lightStyle" ) || !Q_stricmp( token, "vmap_lightStyle" ) ) { + GetTokenAppend( shaderText, qfalse ); + val = atoi( token ); + if ( val < 0 ) { + val = 0; + } + else if ( val > LS_NONE ) { + val = LS_NONE; + } + si->lightStyle = val; + } + + /* wolf: q3map_lightRGB */ + else if ( !Q_stricmp( token, "q3map_lightRGB" ) || !Q_stricmp( token, "vmap_lightRGB" )) { + VectorClear( si->color ); + GetTokenAppend( shaderText, qfalse ); + si->color[ 0 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->color[ 1 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->color[ 2 ] = atof( token ); + if ( colorsRGB ) { + si->color[0] = Image_LinearFloatFromsRGBFloat( si->color[0] ); + si->color[1] = Image_LinearFloatFromsRGBFloat( si->color[1] ); + si->color[2] = Image_LinearFloatFromsRGBFloat( si->color[2] ); + } + ColorNormalize( si->color, si->color ); + } + + /* q3map_lightSubdivide */ + else if ( !Q_stricmp( token, "q3map_lightSubdivide" ) || !Q_stricmp( token, "vmap_lightSubdivide" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->lightSubdivide = atoi( token ); + } + + /* q3map_backsplash */ + else if ( !Q_stricmp( token, "q3map_backsplash" ) || !Q_stricmp( token, "vmap_backsplash" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->backsplashFraction = atof( token ) * 0.01f; + GetTokenAppend( shaderText, qfalse ); + si->backsplashDistance = atof( token ); + } +#if 0 + /* q3map_floodLight */ + else if ( !Q_stricmp( token, "q3map_floodLight" ) || !Q_stricmp( token, "vmap_floodLight" ) ) { + /* get color */ + GetTokenAppend( shaderText, qfalse ); + si->floodlightRGB[ 0 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->floodlightRGB[ 1 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->floodlightRGB[ 2 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->floodlightDistance = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->floodlightIntensity = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->floodlightDirectionScale = atof( token ); + if ( colorsRGB ) { + si->floodlightRGB[0] = Image_LinearFloatFromsRGBFloat( si->floodlightRGB[0] ); + si->floodlightRGB[1] = Image_LinearFloatFromsRGBFloat( si->floodlightRGB[1] ); + si->floodlightRGB[2] = Image_LinearFloatFromsRGBFloat( si->floodlightRGB[2] ); + } + ColorNormalize( si->floodlightRGB, si->floodlightRGB ); + } +#endif + /* jal: q3map_nodirty : skip dirty */ + else if ( !Q_stricmp( token, "q3map_nodirty" ) || !Q_stricmp( token, "vmap_nodirty" ) ) { + si->noDirty = qtrue; + } + + /* q3map_lightmapSampleSize */ + else if ( !Q_stricmp( token, "q3map_lightmapSampleSize" ) || !Q_stricmp( token, "vmap_lightmapSampleSize" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->lightmapSampleSize = atoi( token ); + } + + /* q3map_lightmapSampleOffset */ + else if ( !Q_stricmp( token, "q3map_lightmapSampleOffset" ) || !Q_stricmp( token, "vmap_lightmapSampleOffset" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->lightmapSampleOffset = atof( token ); + } + + /* ydnar: q3map_lightmapFilterRadius */ + else if ( !Q_stricmp( token, "q3map_lightmapFilterRadius" ) || !Q_stricmp( token, "vmap_lightmapFilterRadius" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->lmFilterRadius = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->lightFilterRadius = atof( token ); + } + + /* ydnar: q3map_lightmapAxis [xyz] */ + else if ( !Q_stricmp( token, "q3map_lightmapAxis" ) || !Q_stricmp( token, "vmap_lightmapAxis" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( !Q_stricmp( token, "x" ) ) { + VectorSet( si->lightmapAxis, 1, 0, 0 ); + } + else if ( !Q_stricmp( token, "y" ) ) { + VectorSet( si->lightmapAxis, 0, 1, 0 ); + } + else if ( !Q_stricmp( token, "z" ) ) { + VectorSet( si->lightmapAxis, 0, 0, 1 ); + } + else + { + Sys_FPrintf( SYS_WRN, "WARNING: Unknown value for lightmap axis: %s\n", token ); + VectorClear( si->lightmapAxis ); + } + } + + /* ydnar: q3map_lightmapSize (for autogenerated shaders + external tga lightmaps) */ + else if ( !Q_stricmp( token, "q3map_lightmapSize" ) || !Q_stricmp( token, "vmap_lightmapSize" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->lmCustomWidth = atoi( token ); + GetTokenAppend( shaderText, qfalse ); + si->lmCustomHeight = atoi( token ); + + /* must be a power of 2 */ + if ( ( ( si->lmCustomWidth - 1 ) & si->lmCustomWidth ) || + ( ( si->lmCustomHeight - 1 ) & si->lmCustomHeight ) ) { + Sys_FPrintf( SYS_WRN, "WARNING: Non power-of-two lightmap size specified (%d, %d)\n", + si->lmCustomWidth, si->lmCustomHeight ); + si->lmCustomWidth = lmCustomSize; + si->lmCustomHeight = lmCustomSize; + } + } + + /* ydnar: q3map_lightmapBrightness N (for autogenerated shaders + external tga lightmaps) */ + else if ( !Q_stricmp( token, "q3map_lightmapBrightness" ) || !Q_stricmp( token, "q3map_lightmapGamma" ) || !Q_stricmp( token, "vmap_lightmapBrightness" ) || !Q_stricmp( token, "vmap_lightmapGamma" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->lmBrightness = atof( token ); + if ( si->lmBrightness < 0 ) { + si->lmBrightness = 1.0; + } + } + + /* q3map_vertexScale (scale vertex lighting by this fraction) */ + else if ( !Q_stricmp( token, "q3map_vertexScale" ) || !Q_stricmp( token, "vmap_vertexScale" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->vertexScale = atof( token ); + } + + /* q3map_noVertexLight */ + else if ( !Q_stricmp( token, "q3map_noVertexLight" ) || !Q_stricmp( token, "vmap_noVertexLight" ) ) { + si->noVertexLight = qtrue; + } + + /* q3map_flare[Shader] */ + else if ( !Q_stricmp( token, "q3map_flare" ) || !Q_stricmp( token, "vmap_flare" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] != '\0' ) { + si->flareShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->flareShader, token ); + } + } + + /* q3map_backShader */ + else if ( !Q_stricmp( token, "q3map_backShader" ) || !Q_stricmp( token, "vmap_backShader" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] != '\0' ) { + si->backShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->backShader, token ); + } + } + + /* ydnar: q3map_cloneShader */ + else if ( !Q_stricmp( token, "q3map_cloneShader" ) || !Q_stricmp( token, "vmap_cloneShader" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] != '\0' ) { + si->cloneShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->cloneShader, token ); + } + } + + /* q3map_remapShader */ + else if ( !Q_stricmp( token, "q3map_remapShader" ) || !Q_stricmp( token, "vmap_remapShader" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] != '\0' ) { + si->remapShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->remapShader, token ); + } + } + + /* q3map_deprecateShader */ + else if ( !Q_stricmp( token, "q3map_deprecateShader" ) || !Q_stricmp( token, "vmap_deprecateShader" ) ) { + GetTokenAppend( shaderText, qfalse ); + if ( token[ 0 ] != '\0' ) { + + si->deprecateShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->deprecateShader, token ); + } + } + + /* ydnar: q3map_offset */ + else if ( !Q_stricmp( token, "q3map_offset" ) || !Q_stricmp( token, "vmap_offset" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->offset = atof( token ); + } + + /* ydnar: q3map_fur */ + else if ( !Q_stricmp( token, "q3map_fur" ) || !Q_stricmp( token, "vmap_fur" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->furNumLayers = atoi( token ); + GetTokenAppend( shaderText, qfalse ); + si->furOffset = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->furFade = atof( token ); + } + + /* ydnar: gs mods: legacy support for terrain/terrain2 shaders */ + else if ( !Q_stricmp( token, "q3map_terrain" ) || !Q_stricmp( token, "vmap_terrain" ) ) { + /* team arena terrain is assumed to be nonplanar, with full normal averaging, + passed through the metatriangle surface pipeline, with a lightmap axis on z */ + si->legacyTerrain = qtrue; + si->noClip = qtrue; + si->notjunc = qtrue; + si->indexed = qtrue; + si->nonplanar = qtrue; + si->forceMeta = qtrue; + si->shadeAngleDegrees = 179.0f; + //% VectorSet( si->lightmapAxis, 0, 0, 1 ); /* ydnar 2002-09-21: turning this off for better lightmapping of cliff faces */ + } + + /* ydnar: picomodel: q3map_forceMeta (forces brush faces and/or triangle models to go through the metasurface pipeline) */ + else if ( !Q_stricmp( token, "q3map_forceMeta" ) || !Q_stricmp( token, "vmap_forceMeta" ) ) { + si->forceMeta = qtrue; + } + + /* ydnar: gs mods: q3map_shadeAngle */ + else if ( !Q_stricmp( token, "q3map_shadeAngle" ) || !Q_stricmp( token, "vmap_shadeAngle" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->shadeAngleDegrees = atof( token ); + } + + /* ydnar: q3map_textureSize (substitute for q3map_lightimage derivation for terrain) */ + else if ( !Q_stricmp( token, "q3map_textureSize" ) || !Q_stricmp( token, "vmap_textureSize" ) ) { + GetTokenAppend( shaderText, qfalse ); + si->shaderWidth = atoi( token ); + GetTokenAppend( shaderText, qfalse ); + si->shaderHeight = atoi( token ); + } + + /* ydnar: gs mods: q3map_tcGen